ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

CF Round Goodbye 2021 部分题解

2022-08-19 00:33:49  阅读:152  来源: 互联网

标签:sz ch int 题解 MN CF 2021 Mod define


传送门

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\):

  • 若 \(x\) 的第 \(d\) 位为 \(0\),则不能在两个子树内同时选数,直接把两个子树的 DP 值相加即可。

  • 若 \(x\) 的第 \(d\) 位为 \(1\),则在同一个子树内选的数之间没有限制,但是在不同子树内选的数之间有限制。这个时候我们发现这个 DP 寄了!

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

  • 若 \(x\) 的第 \(d\) 位为 \(1\),那么 \(u_1\) 的左儿子和 \(u_2\) 的左儿子的子树内的数异或起来一定不会大于 \(x\),\(u_1\) 的右儿子和 \(u_2\) 的右儿子同理。所以现在我们只需要考虑 \(u_1\) 的左儿子和 \(u_2\) 的右儿子,以及 \(u_1\) 的右儿子和 \(u_2\) 的左儿子之间的限制,直接直接把两个 \(g\) 值相乘即可。

  • 若 \(x\) 的第 \(d\) 位为 \(0\),\(u_1\) 的左儿子和 \(u_2\) 的右儿子里就不能同时有数,\(u_1\) 的右儿子和 \(u_2\) 的左儿子同理。所以我们把 \(u_1\) 的左儿子和 \(u_2\) 的左儿子的 DP 值,以及 \(u_1\) 的右儿子和 \(u_2\) 的右儿子的 DP 值相加。还有一种情况是只在 \(u_1\) 或 \(u_2\) 的子树里选数,这时可以任意选,简单算算就行了。

解决本题的关键观察是发现所有限制事实上只会和至多 \(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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有