CF #754 部分题解

传送门

CF1605D Treelabeling

显然,能够在 \(u\)\(v\) 之间移动当且仅当 \(u\)\(v\) 最高位 \(1\) 对应的位置相同。使用反证法容易证明。

按照最高位 \(1\) 的位置将所有点分成若干个集合,我们的目标是让同一个集合内的点尽量不相邻。事实上存在如下构造方法使得所有相邻节点都属于不同集合:注意到 \(\lfloor \frac{2^k - 1}{2} \rfloor \leq 2^{k - 1} - 1\),将树黑白染色,设 \(k\) 为满足 \(n < 2^x\) 的最小的 \(x\),那么黑点和白点数目中较小的一个一定不大于 \(\lfloor \frac{2^k - 1}{2} \rfloor\),而 \(1 \sim n\) 中二进制最高位低于 \(k\) 的数恰有 \(2^{k - 1} - 1\) 个,因此对编号二进制分组后一定能表示较小的那个点数。

Code
/*
也许所有的执念 就像四季的更迭
没有因缘 不需致歉
是否拥抱着告别 就更能读懂人间
还是感慨 更多一点
*/
#include <bits/stdc++.h>
#define pii pair<int, int>
#define mp(x, y) make_pair(x, y)
#define pb push_back
#define fi first
#define se second
#define int long long
#define mem(x, v) memset(x, v, sizeof(x))
#define mcpy(x, y, n) memcpy(x, y, sizeof(int) * (n))
#define lob lower_bound
#define upb upper_bound
using namespace std;

inline int read() {
	int x = 0, w = 1;char ch = getchar();
	while (ch > '9' || ch < '0') { if (ch == '-')w = -1;ch = getchar(); }
	while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
	return x * w;
}

inline int min(int x, int y) { return x < y ? x : y; }
inline int max(int x, int y) { return x > y ? x : y; }

const int MN = 4e5 + 5;
const int Mod = 1e9;
const int Inf = 2e18;

inline void Add(int &x, int y) { x += y; if (x >= Mod) x -= Mod; }
inline void Dec(int &x, int y) { x -= y; if (x < 0) x += Mod; }

inline int qPow(int a, int b = Mod - 2, int ret = 1) {
    while (b) {
        if (b & 1) ret = ret * a % Mod;
        a = a * a % Mod, b >>= 1;
    }
    return ret;
}

// #define dbg

int N, ans[MN], dep[MN], vis[MN];
vector <int> G[MN], L, R;

inline void DFS(int u, int pr) {
    dep[u] = dep[pr] + 1;
    ((dep[u] & 1) ? L : R).pb(u);
    for (int v : G[u]) if (v != pr) DFS(v, u);
}
inline void Work() {
    N = read();
    for (int i = 1; i <= N; i++) dep[i] = vis[i] = 0, G[i].clear();
    L.clear(), R.clear();
    for (int i = 1, u, v; i < N; i++) u = read(), v = read(), G[u].pb(v), G[v].pb(u);
    DFS(1, 0);
    int sl = L.size(), sr = R.size();
    int o = 1;
    if (sl >= sr) swap(sl, sr), swap(L, R); 
    while (sl) {
        if (sl & 1) for (int i = (1 << (o - 1)); i <= (1 << o) - 1; i++)
            ans[L.back()] = i, L.pop_back(), vis[i] = 1;
        o++, sl >>= 1;
    }
    for (int i = 1; i <= N; i++) if (!vis[i]) ans[R.back()] = i, R.pop_back();
    for (int i = 1; i <= N; i++) printf("%lld%c", ans[i], " \n"[i == N]);
}

signed main(void) {
    int Tests = read();
    while (Tests--) Work();
    return 0; 
}

CF1605E Array Equalizer

显然从前往后依次操作是最优的。令 \(c_i = a_i - b_i\),设 \(f_i\) 为在 \(i\) 处做的操作次数,那么有:

\[f_i = c_i - \sum_{d | i, d \neq i} f_d \]

也就是 \(c_i = \sum_{d | i} f_d\),那么根据莫比乌斯反演:

\[f_i = \sum_{d | i} \mu ( \frac{i}{d} ) c_d \]

答案是 \(\sum |f_i|\)。为了处理多组询问,我们可以先把 \(f_i\) 除了 \(\mu(i) c_1\) 这一项的值算出来,每次修改只需要加上 \(\mu(i) c_1\) 的贡献。注意到 \(\mu(i) \in \{ -1,0,1 \}\),可以把 \(f_i\) 按照 \(\mu(i)\) 的值分类,\(\mu(i) = 0\) 直接算,\(\mu(i) = 1\)\(\mu(i) = -1\) 的情况可以将所有 \(f_i\) 排序,每次查询在上面二分,容易 \(O(1)\) 求得答案。

Code
/*
也许所有的执念 就像四季的更迭
没有因缘 不需致歉
是否拥抱着告别 就更能读懂人间
还是感慨 更多一点
*/
#include <bits/stdc++.h>
#define pii pair<int, int>
#define mp(x, y) make_pair(x, y)
#define pb push_back
#define fi first
#define se second
#define int long long
#define mem(x, v) memset(x, v, sizeof(x))
#define mcpy(x, y, n) memcpy(x, y, sizeof(int) * (n))
#define lob lower_bound
#define upb upper_bound
using namespace std;

inline int read() {
	int x = 0, w = 1;char ch = getchar();
	while (ch > '9' || ch < '0') { if (ch == '-')w = -1;ch = getchar(); }
	while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
	return x * w;
}

inline int min(int x, int y) { return x < y ? x : y; }
inline int max(int x, int y) { return x > y ? x : y; }

const int MN = 2e5 + 5;
const int Mod = 1e9;
const int Inf = 2e18;

inline void Add(int &x, int y) { x += y; if (x >= Mod) x -= Mod; }
inline void Dec(int &x, int y) { x -= y; if (x < 0) x += Mod; }

inline int qPow(int a, int b = Mod - 2, int ret = 1) {
    while (b) {
        if (b & 1) ret = ret * a % Mod;
        a = a * a % Mod, b >>= 1;
    }
    return ret;
}

// #define dbg

int N, Q, a[MN], b[MN], s1[MN], s2[MN], f[MN], s;
vector <int> v1, v2;

int pr[MN], tot, isp[MN], mu[MN];
inline void Init() {
    mu[1] = 1;
    for (int i = 2; i < MN; i++) {
        if (!isp[i]) pr[++tot] = i, mu[i] = -1;
        for (int j = 1; j <= tot && i * pr[j] < MN; j++) {
            isp[i * pr[j]] = 1;
            if (i % pr[j] == 0) { mu[i * pr[j]] = 0; break; }
            mu[i * pr[j]] = -mu[i];
        }
    }
}

signed main(void) {
    Init();
    N = read();
    for (int i = 1; i <= N; i++) a[i] = read();
    for (int i = 1; i <= N; i++) b[i] = read();
    b[1] = 0;
    for (int i = 2; i <= N; i++)   
        for (int j = i; j <= N; j += i) f[j] += mu[j / i] * (b[i] - a[i]);
    for (int i = 2; i <= N; i++) 
        if (mu[i] == -1) v1.pb(f[i]);
        else if (mu[i] == 1) v2.pb(f[i]);
        else s += abs(f[i]);
    sort(v1.begin(), v1.end());
    sort(v2.begin(), v2.end());
    for (int i = 0; i < v1.size(); i++) s1[i + 1] = s1[i] + v1[i];
    for (int i = 0; i < v2.size(); i++) s2[i + 1] = s2[i] + v2[i];
    Q = read();
    while (Q--) {
        int x = read() - a[1], ans = abs(x);
        int y = lob(v1.begin(), v1.end(), x) - v1.begin();
        ans += x * y - 2 * s1[y] + s1[v1.size()] - x * (v1.size() - y);
        int z = lob(v2.begin(), v2.end(), -x) - v2.begin();
        ans += - x * z - 2 * s2[z] + s2[v2.size()] + x * (v2.size() - z);
        printf("%lld\n", ans + s);
    }
    return 0; 
}

CF1605F PalindORme

神仙题。考虑如何刻画一个合法序列,可以发现每个合法序列都能够通过以下操作直到剩下不超过 \(1\) 个数:

  1. 令变量 \(v = 0\)
  2. 从序列中选出两个数 \(x,y\) 使得 \(x \ \mathrm{or} \ v = y \ \mathrm{or} \ v\)
  3. \(v \leftarrow x \ \mathrm{or} \ v\),然后删除 \(x,y\) 并回到第二步。

发现还是不好计数,但是可以尝试建立从不合法序列到合法序列的映射,然后容斥算。考察一个不合法序列经过若干次操作后剩下的序列(如果存在某个数 \(x\) 满足 \(x \ \mathrm{or} \ v = x\),我们也将它移除),可以发现剩下的数满足:去掉 \(v\) 二进制为 \(1\) 的位置后,任意数不为 \(0\) 且两两不同。于是我们成功地建立了映射,接下来考虑如何计数。

\(f_{i,j}\) 为长度为 \(i\),二进制恰有 \(j\) 位的序列数,\(g_{i,j}\) 为长度为 \(i\),二进制恰有 \(j\) 位的不合法序列数,对于 \(f_{i,j}\) 我们容斥:

\[f_{i,j} = \sum_{k = 0}^{j} (-1)^{j - k} \binom{j}{k} (2^k) ^i \]

对于 \(g_{i,j}\) 我们枚举其剩下的数的个数及二进制的位数:

\[g_{i,j} = \sum_{i' = 0}^{i-1} \sum_{j'=0}^{j-1} \binom{i}{i'} \binom{j}{j'} (2^{j'})^{i - i'} h_{i - i', j - j'} (f_{i',j'} - g_{i',j'}) \]

其中 \((2^{j'})^{i - i'}\) 算的是被删去的二进制位,\(h_{i,j}\) 为长度为 \(i\),二进制数量恰好为 \(j\),任意数非 \(0\) 且两两不同的方案数,可以直接容斥:

\[h_{i,j} = \sum_{k = 0} ^{j} (-1)^{j-k} \binom{j}{k} (2^k - 1)^{\underline{i}} \]

然后就做完了。不过还有一点细节,一个长度为奇数的不合法序列不能从它长度减一的合法序列转移过来,因为显然这也是合法的。

Code
/*
也许所有的执念 就像四季的更迭
没有因缘 不需致歉
是否拥抱着告别 就更能读懂人间
还是感慨 更多一点
*/
#include <bits/stdc++.h>
#define pii pair<int, int>
#define mp(x, y) make_pair(x, y)
#define pb push_back
#define fi first
#define se second
#define int long long
#define mem(x, v) memset(x, v, sizeof(x))
#define mcpy(x, y, n) memcpy(x, y, sizeof(int) * (n))
#define lob lower_bound
#define upb upper_bound
using namespace std;

inline int read() {
	int x = 0, w = 1;char ch = getchar();
	while (ch > '9' || ch < '0') { if (ch == '-')w = -1;ch = getchar(); }
	while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
	return x * w;
}

inline int min(int x, int y) { return x < y ? x : y; }
inline int max(int x, int y) { return x > y ? x : y; }

const int MN = 105;
const int Mod = 1e9;
const int Inf = 2e18;

inline void Add(int &x, int y) { x += y; if (x >= Mod) x -= Mod; }
inline void Dec(int &x, int y) { x -= y; if (x < 0) x += Mod; }

inline int qPow(int a, int b = Mod - 2, int ret = 1) {
    while (b) {
        if (b & 1) ret = ret * a % Mod;
        a = a * a % Mod, b >>= 1;
    }
    return ret;
}

// #define dbg

int N, M, Ans, P;
int f[MN][MN], g[MN][MN], h[MN][MN], pw2[MN * MN], C[MN][MN], Coef[5];

inline int calc(int x, int n) {
    int res = 1;
    for (int i = x; i >= x - n + 1; i--) res = res * i % P;
    return res;
}

signed main(void) {
    N = read(), M = read(), P = read();
    for (int i = 0; i <= 100; i++) {
        C[i][0] = C[i][i] = 1;
        for (int j = 1; j < i; j++)
            C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % P;
    }
    pw2[0] = 1;
    for (int i = 1; i <= N * M; i++)
        pw2[i] = pw2[i - 1] * 2 % P;
    Coef[0] = 1, Coef[1] = P - 1;
    for (int i = 0; i <= N; i++)
        for (int j = 0; j <= M; j++)
            for (int k = 0; k <= j; k++) 
                f[i][j] = (f[i][j] + C[j][k] * Coef[(j - k) & 1] % P * pw2[i * k] % P) % P,
                h[i][j] = (h[i][j] + C[j][k] * Coef[(j - k) & 1] % P * calc(pw2[k] - 1, i) % P) % P;
    for (int i = 0; i <= N; i++)
        for (int j = 0; j <= M; j++)
            for (int x = 0; x < i; x++)
                for (int y = 0; y < j; y++) {
                    if ((N & 1) && (i == N) && (x == N - 1)) continue;
                    g[i][j] = (g[i][j] + C[i][x] * C[j][y] % P * pw2[(i - x) * y] % P * h[i - x][j - y] % P * (f[x][y] - g[x][y] + P) % P) % P;
                }
    for (int i = 0; i <= M; i++) Ans = (Ans + C[M][i] * (f[N][i] - g[N][i] + P) % P) % P;
    printf("%lld\n", Ans);
    return 0; 
}
posted @ 2022-07-08 10:04  came11ia  阅读(40)  评论(0编辑  收藏  举报