其他分享
首页 > 其他分享> > CF Round Goodbye 2021 部分题解

CF Round Goodbye 2021 部分题解

作者:互联网

传送门

CF1616F Tricolor Triangles

诈骗题。限制相当于每个三元环三条边的 \(c_i\) 之和能被 \(3\) 整除,将每条边的 \(c_i\) 看做一个未知数,那么问题就是要求解若干个模 \(3\) 意义下的方程组。根据经典结论我们知道三元环最多有 \(O(m \sqrt m)\) 个,直接高斯消元复杂度为 \(O(m^3 \sqrt m)\),跑不满。当然也可以使用 bitset 优化。

Code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
#define pii pair<int, int>
#define mp(x, y) make_pair(x, y)
#define pb push_back
#define eb emplace_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 = 260;
const int MM = 4360;
const int Mod = 1e9 + 7;

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, cnt, r, e[MN][MN], a[MM][MN], ans[MN];
inline void Work() {
    N = read(), M = read();
    cnt = r = 0;
    mem(e, 0), mem(a, 0);
    for (int i = 1; i <= M; i++) {
        int x = read(), y = read(), w = read();
        e[x][y] = e[y][x] = i;
        if (~w) cnt++, a[cnt][i] = 1, a[cnt][M + 1] = w % 3;
    }
    for (int i = 1; i <= N; i++) 
        for (int j = i + 1; j <= N; j++)
            for (int k = j + 1; k <= N; k++)
                if (e[i][j] && e[j][k] && e[i][k]) cnt++, a[cnt][e[i][j]] = a[cnt][e[j][k]] = a[cnt][e[i][k]] = 1;
    for (int i = 1; i <= M; i++) {
        int p = 0;
        for (int j = r + 1; j <= cnt; j++) if (a[j][i]) p = j;
        if (!p) { ans[i] = 0; continue; }
        swap(a[++r], a[p]);
        if (a[r][i] != 1) for (int j = i; j <= M + 1; j++) a[r][j] = 3 - a[r][j];
        for (int j = 1; j <= cnt; j++) 
            if (r != j && a[j][i]) {
                int x = a[j][i];
                for (int k = i; k <= M + 1; k++) a[j][k] = (a[j][k] - x * a[r][k] + 9) % 3;
            }
    }
    for (int i = r + 1; i <= cnt; i++)
        if (a[i][M + 1]) return puts("-1"), void();
    for (int i = 1; i <= r; i++) {
        int p = i;
        while (!a[i][p]) p++;
        ans[p] = a[i][M + 1];
    }
    for (int i = 1; i <= M; i++) printf("%lld%c", ans[i] ? ans[i] : 3, " \n"[i == M]);
}

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

CF1616G Just Add an Edge

先特判原图中存在哈密顿路的情况,此时答案显然为 \(\binom{n}{2}\)。不妨假设图中不存在哈密顿路,考虑添加 \(x \to y(x > y)\) 后哈密顿路的形态,其一定形如:

\[\boxed{1 \to 2 \to \cdots \to y-1} \rightsquigarrow x \to y \rightsquigarrow \boxed{x+1 \to x+2 \to \cdots \to n} \]

其中 \(y-1 \rightsquigarrow x\) 和 \(y \rightsquigarrow x+1\) 的链不能有交,且并为 \([y-1,x+1]\)。因此如果记录有序二元组 \((y-1,y)\) 能否通过两条不交的且并为 \([y-1,x+1]\) 的链与 \((x,x+1)\) 连通,再检查 \(1 \sim y-2\) 以及 \(x+1 \sim n-1\) 是否均有 \(i \to i+1\) 这条边,从而判断 \(x \to y\) 是否合法。至此,我们得到了 \(x \to y\) 的充要条件。

考虑枚举每个 \((y-1,y)\),统计其右边有多少个 \((x-1,x)\) 满足条件。不难发现 \(y-1 \rightsquigarrow x\) 和 \(y \rightsquigarrow x+1\) 的链呈现出相互交错的形态,每次一条链往前扩展,另一条链填满中间的空隙。于是可以直接 DP,设 \(f_{i,0/1}\) 分别表示 \((y-1,y)\) 能否到达 \((i,i+1)\) 或 \((i+1,i)\)(两种状态分别表示哪条链在前面),转移若 \(i \to u+1\) 有边且存在链 \(i+1 \to i+2 \to \cdots \to u\),那么 \(f_{u,k \oplus 1} \gets f_{i,k}\)。最后一个条件可以求出 \(r_i\) 表示最后一个存在链 \(i \to i+1 \to \cdots \to j\) 的节点 \(j\),那么该条件即 \(r_{i+1} \geq u\)。直接对每个 \((y-1,y)\) 都做一遍时间复杂度为 \(O(n^2)\),可以用 bitset 优化到 \(O(\frac{n^2}{\omega})\),但仍然无法通过。

继续观察性质,我们发现在 \(p \nrightarrow p+1\) 的位置一定会产生一个断点,此时不存在 \(i < p\) 能够转移到 \(u > p\) 的位置,因此左右两部分是独立的。不妨假设 \(p\) 是所有这样的断点中最小的一个。根据哈密顿路的形态,我们也容易发现所有合法的 \(x \to y\) 一定满足 \(x \geq \mathrm{last} \land y \leq p+1\),其中 \(\mathrm{last}\) 为最后一个使得 \(r_i = n\) 的位置 \(i\),这是为了保证存在路径 \(x+1 \to x+2 \to \cdots \to n\)。而由于 \(p\) 是最左侧的断点,因此有 \(r_1 = p\),为了保证存在路径 \(1 \to 2 \to \cdots \to y-1\) 需要满足 \(y \leq p\)。

事实上,根据连边的方式我们能够得到一个更重要的结论:任意一条 \((y-1,y) \rightsquigarrow (x,x+1)\) 的路径一定经过 \((p+1,p)\) 或 \((p,p+1)\)。于是我们可以对左右两边建正反图分别处理,同样使用 DP,不过对状态的定义稍作修改:\(f_{i,0/1}\) 表示从 \((p,p+1)\) 能否到达 \((i,i+1)\) 或 \((i+1,i)\),根据可达性的传递性,\(x \to y\) 合法当且仅当 \(f_{x,0} \land f_{y-1,0}\) 或 \(f_{x,1} \land f_{y-1,1}\)(显然两条链头尾的相对顺序应该相同)。

对其容斥,用满足 \(f_{x, 0}\) 的 \(x\) 的个数乘上满足 \(f_{y - 1, 0}\) 的 \(y - 1\) 的个数,加上满足 \(f_{x, 1}\) 的 \(x\) 的个数乘上满足 \(f_{y - 1, 1}\) 的 \(u - 1\) 的个数,这样满足 \(f_{x,0} \land f_{x,1} \land f_{y,0} \land f_{y,1}\) 的 \(x \to y\) 会被计算两次,再减去满足 \(f_{x, 0} \land f_{x, 1}\) 的 \(x\) 的个数乘上满足 \(f_{y - 1, 0} \land f_{y - 1, 1}\) 的 \(y\) 的个数即可。

一些细节:当 \(y=1\) 时 \(f_{y-1}\) 没有定义,当 \(x = n\) 时同理。一种解决方法是添加虚点 \(0\) 和 \(n+1\),从 \(0\) 向所有 \(1 \sim n\) 连边,并从 \(1 \sim n\) 向 \(n+1\) 连边,这样就规定了哈密顿路的起点和终点,此时 \(f_{1,0/1}\) 和 \(f_{n,0/1}\) 就是良定义的了。还有一个特殊情况是,当整张图只有 \(p \nrightarrow\) 这个断点时,\(p \to p+1\) 这条边会被算进答案,需要特判一下。

综上,我们在 \(O(n)\) 的时间复杂度内解决了这个问题。

Code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
#define pii pair<int, int>
#define mp(x, y) make_pair(x, y)
#define pb push_back
#define eb emplace_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;
}

const int MN = 2e5 + 5;
const int Mod = 998244353;
const int inf = 1e9;

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

vector <int> e[MN], rev[MN];
int N, M, r[MN]; bool tag[MN], f[MN][2];

inline void work() {
    N = read(), M = read();
    mem(tag, 0);
    mem(f, 0);
    for (int i = 0; i <= N; i++) e[i].clear(), rev[i].clear();
    for (int i = 2; i <= N; i++) rev[i].pb(0);
    for (int i = 1; i < N; i++) e[i].pb(N + 1);
    for (int i = 1, u, v; i <= M; i++) {
        u = read(), v = read();
        if (u + 1 == v) tag[u] = 1;
        else e[u].pb(v), rev[v].pb(u);
    }
    r[N + 1] = N + 1, tag[0] = tag[N] = 1;
    for (int i = N; ~i; i--) r[i] = tag[i] ? r[i + 1] : i;
    if (r[0] == N + 1) return printf("%lld\n", N * (N - 1) / 2), void();
    int p = r[0], lst = N + 1;
    f[p][1] = 1;
    while (tag[lst - 1]) lst--;
    for (int i = p; i <= N; i++)
        for (int v : e[i])
            if (r[i + 1] >= v - 1)
                for (int o = 0; o < 2; o++)
                    f[v - 1][o] |= f[i][o ^ 1];
    for (int i = p + 1; i; i--)
        for (int v : rev[i])
            if (r[v + 1] >= v - 1)
                for (int o = 0; o < 2; o++)
                    f[v][o] |= f[i - 1][o ^ 1];
    int ans = 0, L, R;
    for (int o = 0; o < 2; o++) {
        L = R = 0;
        for (int i = 0; i <= p; i++) L += f[i][o];
        for (int i = lst - 1; i <= N; i++) R += f[i][o];
        ans += L * R;
    }
    L = R = 0;
    for (int i = 0; i <= p; i++) L += f[i][0] && f[i][1];
    for (int i = lst - 1; i <= N; i++) R += f[i][0] && f[i][1];
    ans -= L * R + (r[p + 1] == N + 1);
    printf("%lld\n", ans);
}

signed main(void) {
    int T = read();
    while (T--) work();
    return 0;
}

CF1616H Keep XOR Low

考虑在 Trie 上 DP。按位考虑,设 \(f_u\) 表示在 \(u\) 子树内选若干个点,两两之间异或不大于 \(x\) 的方案数。考虑转移,设 \(u\) 代表的位是 \(d\):

不妨直接把这两个互相有限制的子树设进状态,具体来说设 \(g_{u_1,u_2}\) 表示在 \(u_1\) 和 \(u_2\) 里各选若干个数组成集合 \(S,T\),对任意 \(i \in S,j \in T\) 满足 \(i \oplus j \leq x\) 的方案数。此时:

解决本题的关键观察是发现所有限制事实上只会和至多 \(2\) 颗子树有关。这样 DP 的好处是,每次我们转移的时候都保证了在更高位上是合法的,于是我们每次都只需考虑当前层的限制,而不需要考虑子树内的限制。由于每个点只会被经过一遍,所以总时间复杂度为 Trie 树的点数,即 \(O(n \log n)\)。细节详见代码。

Code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
#define pii pair<int, int>
#define mp(x, y) make_pair(x, y)
#define pb push_back
#define eb emplace_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 MS = MN << 5;
const int Mod = 998244353;

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, x, cnt = 1, a[MN], ch[MS][2], sz[MS], pw[MN];
inline void insert(int x) {
    int p = 1;
    sz[p]++;
    for (int i = 30; ~i; i--) {
        int c = x >> i & 1;
        if (!ch[p][c]) ch[p][c] = ++cnt;
        sz[p = ch[p][c]]++;
    }
}
inline void add(int &x, int y) {
    x += y; if (x >= Mod) x -= Mod;
}
inline int DFS(int u1, int u2, int d) {
    if (!u1) return pw[sz[u2]];
    if (!u2) return pw[sz[u1]];
    if (u1 == u2) {
        if (d < 0) return pw[sz[u1]];
        int lc = ch[u1][0], rc = ch[u1][1];
        if (x >> d & 1) return DFS(lc, rc, d - 1);
        else return (DFS(lc, lc, d - 1) + DFS(rc, rc, d - 1) - 1 + Mod) % Mod;
    } else {
        if (d < 0) return pw[sz[u1] + sz[u2]];
        int lc1 = ch[u1][0], rc1 = ch[u1][1], lc2 = ch[u2][0], rc2 = ch[u2][1];
        if (x >> d & 1) return DFS(lc1, rc2, d - 1) * DFS(lc2, rc1, d - 1) % Mod;
        else {
            int res = (DFS(lc1, lc2, d - 1) + DFS(rc1, rc2, d - 1) - 1 + Mod) % Mod;
            add(res, (pw[sz[lc1]] - 1 + Mod) * (pw[sz[rc1]] - 1 + Mod) % Mod);
            add(res, (pw[sz[lc2]] - 1 + Mod) * (pw[sz[rc2]] - 1 + Mod) % Mod);
            return res;
        }
    }
}
inline void Work() {
    N = read(), x = read();
    for (int i = 1; i <= N; i++) a[i] = read(), insert(a[i]);
    pw[0] = 1;
    for (int i = 1; i <= N; i++) add(pw[i] = pw[i - 1], pw[i - 1]);
    printf("%lld\n", (DFS(1, 1, 30) - 1 + Mod) % Mod);
}

signed main(void) {
    int T = 1;
    while (T--) Work();
    return 0;
}

标签:sz,ch,int,题解,MN,CF,2021,Mod,define
来源: https://www.cnblogs.com/came11ia/p/16600259.html