[AGC043C] Giant Graph 题解
题意:
给定三个简单无向图,其中每个图的点数均为,边数分别为。
现在根据构造一个新的无向图。有个点,每个点可以表示为,对应中的点,中的点,中的点。边集的构造方式如下:
若中存在一条边,则对于任意,在中添加边;
若中存在一条边,则对于任意,在中添加边;
若中存在一条边,则对于任意,在中添加边.
对于中的任意一个点,定义其点权为。
试求的最大权独立集的大小模的值。
。
分析:
由于点权为 ,显然求最大权独立集时可以直接贪心,因为取编号大的一定更优。
考虑只有 的情况,我们首先把 中的边定向(编号小的连到编号大的),然后可以发现,独立集的定义可以转化成一个经典的博弈问题:给定一张 DAG,两个人轮流操作,最先无法操作的人失败。
因此可以使用 SG 函数解决。对重构图中出度为 的点定义其 ,然后转移 。
这样做后可以发现 值为 的点一定会取,因为根据 的定义,即存在至少一个 为 ,那么 一定不为 。这里先手必败就一定会取。
那这里三个图怎么做呢?可以发现每个点的三个维度的值的变换是独立的。
根据 定理:
对于由 个有向图游戏组成的组合游戏,设它们的起点分别为 ,则有定理:当且仅当 时,这个游戏是先手必胜的。
因此我们需要统计:
这东西就很好做了,记 表示目前处理到 ,异或和为 的所有方案的权值和。
转移显然为 f[i + 1][j ^ a[i].SG[g]] += f[i][j] * h[g]
。
暴力转移是 的。但众所周知,一个 DAG 的 函数的值域在 。因此可以做到 或 的时间复杂度,前者需要搞个 的桶。
代码:
#include<bits/stdc++.h>
#define int long long
#define mod 998244353
#define N 100005
using namespace std;
int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar(); }
while(ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
return x * f;
}
void write(int x) {
if(x < 0) putchar('-'), x = -x;
if(x > 9) write(x / 10);
putchar('0' + x % 10);
}
int Pow(int a, int n) {
if(n == 0) return 1;
if(n == 1) return a;
int x = Pow(a, n / 2);
if(n % 2 == 0) return x * x % mod;
else return x * x % mod * a % mod;
}
int n, k, v, h[N];
int z[N], f[7][N]; //f[i][j]表示考虑前i行,异或和为j的权值和
struct Graph{
vector<int>G[N], p[N];
int outd[N], SG[N], t[N]; //t[i]是SG函数的桶
void work(int x) {
for(auto y : p[x]) z[SG[y]] = 1;
SG[x] = 0; while(z[SG[x]]) SG[x]++;
for(auto y : p[x]) z[SG[y]] = 0;
}
void GetSG() { //拓扑排序求SG函数
queue<int>Q;
for(int i = 1; i <= n; i++)
if(outd[i] == 0) Q.push(i);
while(!Q.empty()) {
int x = Q.front();
Q.pop();
for(auto y : G[x]) {
outd[y]--;
if(outd[y] == 0) work(y), Q.push(y);
}
}
for(int i = 1; i <= n; i++) t[SG[i]] += i;
}
}a[7];
signed main() {
n = read();
k = 3;
v = Pow(10, 18) % mod;
h[0] = 1; for(int i = 1; i <= n; i++) h[i] = h[i - 1] * v % mod;
for(int i = 1; i <= k; i++) {
int mi = read();
while(mi--) {
int u = read(), v = read();
if(u > v) swap(u, v); //小的往大的连边
a[i].G[v].push_back(u); //连反向边
a[i].p[u].push_back(v); //连正向边
a[i].outd[u]++;
}
}
for(int i = 1; i <= k; i++) a[i].GetSG();
f[1][0] = 1;
for(int i = 1; i <= k; i++)
for(int j = 0; j <= 700; j++)
for(int g = 1; g <= n; g++) {
f[i + 1][j ^ a[i].SG[g]] += f[i][j] * h[g];
f[i + 1][j ^ a[i].SG[g]] %= mod;
}
write(f[k + 1][0]);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通