AtCoder Beginner Contest 152

发布时间 2023-05-07 23:55:32作者: Zeoy_kkk

Flatten

给定\(n\)个正整数\(a_i\),,现在让你求出\(n\)个整数\(b_i\),使得任取\(1\le i < j \le n\)\(a_ib_i=a_jb_j\)始终成立,请你求出\(\sum b_i\)的最小值,答案对\(1e9+7\)取模

\(1\le n \le 10^4\)

\(1 \le a_i \le 10^6\)

题解:最大公倍数取模 + 质因数分解 + 快速幂求乘法逆元

  • 容易得到\(a_ib_i = lcm(\sum a_i)\),那么我们现在只要求出\(n\)\(a_i\)的最大公倍数\(lcm\)且对\(1e9+7\)取模的结果,答案即为

\[\sum_{i=1}^{n}\frac{lcm}{a_i} \]

  • 答案中存在除法,但是取模中没有除法,所以我们需要利用快速幂求出\(a_i\)在模\(1e9+7\)下的乘法逆元\(a_i^{-1}\),答案为

\[\sum_{i=1}^{n}lcm*a_i^{-1} \]

  • 现在我们还需要解决最大公倍数取模的问题:

我们对每个\(a_i\)进行质因子分解,取每个质因子中的最大幂次作为最大公倍数的对应质因子的幂次,然后利用快速幂求出最大公倍数对\(1e9+7\)取模的结果

  • 时间复杂度:质因子分解\(O(\sqrt a_i)\),一共\(n\)个数,\(O(n\sqrt a_i)\),最终时间复杂度为\(O(n\sqrt a_i + nlog(1e9))\)
#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 1e6 + 10, M = 4e5 + 10;

int n;
int a[N];
int p[N], idx;
bool vis[N];
unordered_map<int, int> mp2;

int qpow(int a, int b, int p)
{
    int res = 1;
    while (b)
    {
        if (b & 1)
            res = res * a % p;
        a = a * a % p;
        b >>= 1;
    }
    return res % p;
}

void solve()
{
    cin >> n;
    for (int i = 1; i <= n; ++i)
    {
        cin >> a[i];
        int t = a[i];
        unordered_map<int, int> mp;
        for (int j = 2; j <= t / j; ++j)
        {
            while (t % j == 0)
            {
                mp[j]++;
                mp2[j] = max(mp2[j], mp[j]);
                t /= j;
            }
        }
        if (t > 1)
        {
            mp[t]++;
            mp2[t] = max(mp2[t], mp[t]);
        }
    }
    int d = 1;
    for (auto [x, y] : mp2)
    {
        d = d * qpow(x, y, mod) % mod;
    }
    int ans = 0;
    for (int i = 1; i <= n; ++i)
        ans = (ans % mod + (d % mod * qpow(a[i], mod - 2, mod)) % mod) % mod;
    cout << ans << endl;
}
signed main(void)
{
    Zeoy;
    int T = 1;
    // cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}

Tree and Constraints

给你一颗有\(n\)个节点的树,你要给这棵树黑白染色,并且符合\(m\)条限制,每条限制给定\(u\)\(v\),需要满足\(u\)\(v\)的路径上至少有一个黑色边,问有多少种染色方案

\(2≤n≤50\)

\(1≤m≤min(20,2n(n−1))\)

题解:容斥原理 + 状压 + 树上异或 \(O(2^mm)\)

  • 首先染色总方案数\(S\)\(2^{n-1}\),设\(A_i\)为满足第\(i\)个限制条件的合法方案数,题目想让我们求出\(\bigcap_{i=1}^{m}A_i\),即所有限制条件合法方案数的交集,但是比较难求,正难则反,我们不妨从反面考虑该答案:

\(\bar{A_i}\)为不满足第\(i\)个限制条件的不合法方案数,那么我们把题目想让我们求的答案可以转化为

\[S-\bigcup_{i-1}^{m}\bar{A_i} \]

因为\(m\le 20\),容易发现我们可以利用容斥原理求出\(\bigcup_{i-1}^{m}\bar{A_i}\) ,时间复杂度为:\(O(2^mm)\)

  • 那么现在我们要做的就是求出\(\bar{A_i}\),那么我们分析一下:

对于不满足\(u\)\(v\)的限制条件的方案数\(\bar{A_i}\),只要\(u\)\(v\)之间的所有边都涂成白色,那么其他不在\(u\)\(v\)之间的边,颜色就可以随便涂,设\(u\)\(v\)之间的边数为\(cnt\),那么

\[\bar{A_i} = 2^{n-1-cnt} \]

  • 我们已经知道了\(\bar{A_i}\)怎么求,只要知道\(u\)\(v\)之间的边数即可,但是我们在进行容斥的时候我们需要求出多个不合法方案之间的交集,那么交集就意味着我们涂的方案需要使得多个限制条件同时无法满足,即我们需要将所有限制条件里的边都涂成白色,所以说我们需要知道多个限制条件中边的并集,也就是说我们需要知道每个限制条件\(u\)\(v\)之间的边具体有哪些边,我们考虑如何解决这个问题:
    因为\(n \le 50\),所以我们不妨将每个点到根节点的路径中边的状态利用二进制压缩,我们设点\(u\)到根节点的路径中边的状态为\(path_u\),我们设第\(i\)个限制条件\((u,v)\)之间边的信息为\(info_i\),利用树上异或的性质:

\[info_{i} = path_u \bigoplus path_v \]

我们可以求出\(u\)\(v\)之间有哪些边存在

  • 对于多个限制条件中边的并集的状态\(num\)

\[\sum num\ \ |= \ \ info_i \]

  • 设状态\(num\)\(1\)的数量为\(cnt\)(即边的数量),那么对于不满足多个限制条件的方案的交集为

\[2^{n-1-cnt} \]

  • 最后利用容斥原理解题即可
#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 50 + 10, M = 4e5 + 10;

int n, m;
vector<int> g[N];
int path[N];
int info[N];

void dfs(int u, int par)
{
    path[u] = path[par] | (1ll << u);
    for (auto v : g[u])
    {
        if (v == par)
            continue;
        dfs(v, u);
    }
}

void solve()
{
    cin >> n;
    for (int i = 1, u, v; i < n; ++i)
    {
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs(1, 0);
    cin >> m;
    for (int i = 0, u, v; i < m; ++i)
    {
        cin >> u >> v;
        info[i] = path[u] ^ path[v];
    }
    int ans = 0;
    for (int i = 1; i < (1 << m); ++i)
    {
        int cnt = 0;
        int num = 0;
        for (int j = 0; j < m; ++j)
        {
            if (i >> j & 1)
            {
                cnt++;
                num |= info[j];
            }
        }
        int free = n - 1 - __builtin_popcountll(num);
        if (cnt % 2 == 1)
            ans += (1ll << free);
        else
            ans -= (1ll << free);
    }
    cout << (1ll << (n - 1)) - ans << endl;
}
signed main(void)
{
    Zeoy;
    int T = 1;
    // cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}