[AGC043C] Giant Graph 题解

题意:

给定三个简单无向图G1,G2,G3,其中每个图的点数均为n,边数分别为m1,m2,m3

现在根据G1,G2,G3构造一个新的无向图GGn3个点,每个点可以表示为(x,y,z),对应G1中的点xG2中的点yG3中的点z。边集的构造方式如下:

G1中存在一条边(u,v),则对于任意1a,bn,在G中添加边((u,a,b),(v,a,b))

G2中存在一条边(u,v),则对于任意1a,bn,在G中添加边((a,u,b),(a,v,b))

G3中存在一条边(u,v),则对于任意1a,bn,在G中添加边((a,b,u),(a,b,v)).

对于G中的任意一个点(x,y,z),定义其点权为1018(x+y+z)

试求G的最大权独立集的大小模998244353的值。

2n105,1m1,m2,m3105

分析:

由于点权为 1018(x+y+z),显然求最大权独立集时可以直接贪心,因为取编号大的一定更优。

考虑只有 G1 的情况,我们首先把 G1 中的边定向(编号小的连到编号大的),然后可以发现,独立集的定义可以转化成一个经典的博弈问题:给定一张 DAG,两个人轮流操作,最先无法操作的人失败。

因此可以使用 SG 函数解决。对重构图中出度为 0 的点定义其 SG(x)=0,然后转移 SG(x)=mex(SG(y))

这样做后可以发现 SG 值为 0 的点一定会取,因为根据 mex 的定义,即存在至少一个 SG(y)0,那么 SG(x) 一定不为 0。这里先手必败就一定会取。

那这里三个图怎么做呢?可以发现每个点的三个维度的值的变换是独立的。

根据 SG 定理:

对于由 n个有向图游戏组成的组合游戏,设它们的起点分别为 s1,s2,...,sn,则有定理:当且仅当 SG(s1)SG(s2)...SG(sn)0 时,这个游戏是先手必胜的。

因此我们需要统计:

x1=1nx2=1nx3=1nVx1+x2+x3[SG(x1)SG(x2)SG(x3)==0]

这东西就很好做了,记 fi,j 表示目前处理到 xi,异或和为 j 的所有方案的权值和

转移显然为 f[i + 1][j ^ a[i].SG[g]] += f[i][j] * h[g]

暴力转移是 O(n2) 的。但众所周知,一个 DAG 的 SG 函数的值域在 [0,m]。因此可以做到 O(m)O(nm) 的时间复杂度,前者需要搞个 SG 的桶。

代码:

#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;
}
posted @   小超手123  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示