[GDKOI2023 提高组] 异或图 题解

[GDKOI2023 提高组] 异或图

先考虑 \(m=0\) 怎么做,发现问题很像数位 DP,但是由于有 \(n\) 个数,直接 \(O(2^n)\) 记录每个数是否顶到上界非常劣。不过可以借鉴数位 DP 的思想,如果在第 \(w\) 位出现了自由元(第一次脱离上界),不妨设这个数是 \(a_1\),那么 \(b_1\)\([0,w-1]\) 位可以乱填,此时 \(b[2,n]\)\([0,w-1]\) 位随便填都可以通过 \(b_1\) 来调整使得它合法。
于是从大往小枚举 \(w\),钦定 \(\forall b_i\)\(a_i\)\([w+1,\log V]\) 位都相同,第 \(w\) 位出现了一个自由元,计算此时的方案数(如果此时 \(w+1\) 位及以上和 \(C\) 不相同就结束)。可以设计 DP,设 \(dp_{i,0/1,0/1}\) 表示考虑了前 \(i\) 个数,当前第 \(w\) 位的异或和,是否出现过自由元。转移枚举 \(b_i\) 的第 \(w\) 位填什么,如果 \(b_i\) 是第一个自由元那么系数为 \(1\),否则系数为 \(b_i\text{ 在 [0,w-1] 位填数的方案数}\)
这个子问题复杂度 \(O(n\log V)\)


原问题很容易想到 \(O(2^m)\) 进行容斥,对于一个边集 \(E\) 的容斥系数为 \((-1)^{|E|}\),计算答案时考虑每一个连通块,显然一个连通块的限制只跟其中 \(a_i\) 的最小值有关,设为 \(mn\),对于偶数大小连通块直接令答案乘上 \(mn+1\),对于奇数大小连通块把他们的 \(mn\) 全部拿出来跑一开始的子问题。

发现计算答案时我们只关心连通块的情况,并不关心连通块里的边是怎么连的,所以不妨直接枚举连通块是怎么划分的,考虑怎么计算此时的容斥系数:首先不同连通块的容斥系数直接乘起来即可,对于一个连通块 \(S\),设 \(S\) 内的点之间的边的集合为 \(U\),他的容斥系数 \(g_S=\sum_{E\subseteq U,\text{边集 E 能让 S 连通}} (-1)^{|E|}\)。计算 \(g_S\) 可以容斥 \(O(3^n)\) 计算,设 \(h_S=\sum_{E\subseteq U} (-1)^{|E|}=[|U|=0]\),则 \(g_S=h_S-\sum_{T\subsetneqq S,lowbit(T)=lowbit(S)} g_Th_{S/T}\)
直接枚举连通块划分是 \(O(Bell(n)n\log V)\) 的,还是过不去。

沿用之前 \(O(2^m) \to O(Bell(n))\) 的优化思路,计算答案的时候我们需要的只是每个连通块大小的奇偶性以及这个连通块的 \(mn\),至于这个连通块具体是咋样的只会影响容斥系数。为了简便,我们可以把偶数连通块的 \((mn+1)\) 也看成容斥系数的一部分,这样我们就只需要考虑奇数连通块的 \(mn\) 了。
为了更方便的处理 \(mn\) 我们可以把所有点按照 \(a_i\) 升序排序并重标号,如果一个点是奇数连通块的最小值(\(a_i\) 相同比较编号),则称他为关键点,我们可以直接 \(O(2^n)\) 枚举关键点集合 \(S\) 并跑一开始的子问题,现在要解决的仅仅只是 \(S\) 的容斥系数的问题。
划分连通块的过程可以看成是每次选出最小的没有被选择过的点 \(i\) 作为一个新的连通块的 \(mn\),然后从 \([i+1,n]\) 中选择一些没被选过的点和他组成连通块。于是发现对于 \(\le i\) 的点,他们一定已经被划分好了,只需要记录 \(0/1\) 表示他们是否是关键点;对于 \(>i\) 的点,也只需要记录 \(0/1\) 表示他们是否已经被划分到某个连通块中。所以可以直接设状态 \(f_{i,S}\) 表示分界点是 \(i\),每个点的 \(0/1\) 状态为 \(S\) 的容斥系数,我们要的就是 \(f_{n,S}\)。这个 DP 的状态数 \(O(n2^n)\),复杂度 \(O(n3^n)\)

总复杂度 \(O(n3^n+2^nn\log V)\)

code

#include<bits/stdc++.h>
#define Debug puts("-------------------------")
#define LL long long 
using namespace std;
const int N=15+5,mod=998244353;
int lowbit(int x){ return x&(-x); }
void Add(int &x,int y){ x=(x+y>=mod)?(x+y-mod):(x+y); } 
int n,m,siz[(1<<15)+5],dp[N][2][2],f[N][(1<<15)+5],g[(1<<15)+5],h[(1<<15)+5],id[N];
pair<LL,int> a[N];
LL C; 
bool E[N][N];
int DP(vector<LL> &p){
	LL sum=0; for(LL x:p) sum^=x;
	int res=(sum==C),len=p.size();
	for(int w=59;w>=0;w--){ 
		for(int i=1;i<=len;i++) dp[i][0][0]=dp[i][0][1]=dp[i][1][0]=dp[i][1][1]=0;
		LL lst_all=(1ll<<w)-1;
		dp[0][0][0]=1;
		for(int i=0;i<len;i++){
			for(int o1:{0,1}){  
				for(int o2:{0,1}){ 
					int val=dp[i][o1][o2];
					if(!val) continue;
					if(p[i]>>w&1){
						Add(dp[i+1][o1^1][o2],((p[i]&lst_all)+1)%mod*val%mod);
						if(o2) Add(dp[i+1][o1][1],(lst_all+1)%mod*val%mod);
						else Add(dp[i+1][o1][1],val); 
					}	
					else Add(dp[i+1][o1][o2],((p[i]&lst_all)+1)%mod*val%mod);
				}
			}
		}
		Add(res,dp[len][C>>w&1][1]);
		if((sum>>w&1)!=(C>>w&1)) break;
	}
	return res;
}
signed main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	double beg=clock();
	scanf("%d%d%lld",&n,&m,&C);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i].first),a[i].second=i;
	sort(a+1,a+n+1);  
	for(int i=1;i<=n;i++) id[a[i].second]=i;
	for(int i=1;i<=m;i++){
		int u,v; scanf("%d%d",&u,&v); u=id[u],v=id[v];
		E[u][v]=E[v][u]=true;
	}
	
	for(int S=1;S<(1<<n);S++){
		h[S]=1,siz[S]=__builtin_popcount(S);
		for(int i=0;i<n;i++) if(S>>i&1) for(int j=i+1;j<n;j++) if((S>>j&1)&&E[i+1][j+1]) h[S]=0;
		g[S]=h[S];
		for(int T=(S-1)&S;T;T=(T-1)&S) if(lowbit(S)==lowbit(T)) Add(g[S],mod-g[T]*h[S^T]);
	}
	f[0][0]=1;
	for(int i=0,all=(1<<n)-1;i<n;i++){  
		for(int S=0;S<(1<<n);S++){
			if(!f[i][S]) continue;
			if(S>>i&1){  //注意点 i+1 对应第 i 位 
				Add(f[i+1][S^(1<<i)],f[i][S]);
				continue;
			} 
			int S1=S&((1<<i)-1),S2=S^S1,T=(S^all)>>(i+1)<<(i+1);
			for(int S3=T;;S3=(S3-1)&T){
				int val=1ll*f[i][S]*g[(1<<i)^S3]%mod;
				if((siz[S3]+1)&1) Add(f[i+1][S1^(1<<i)^(S2^S3)],val);
				else Add(f[i+1][S1^(S2^S3)],(a[i+1].first+1)%mod*val%mod);
				if(!S3) break;
			}
		}
	}
	int ans=0;
	for(int S=0;S<(1<<n);S++){
		vector<LL> p;
		for(int i=0;i<n;i++) if(S>>i&1) p.emplace_back(a[i+1].first);
		Add(ans,1ll*DP(p)*f[n][S]%mod); 
	} 
	printf("%d\n",ans); 
	cerr << "Time: " << (clock()-beg) << endl;
	return 0;
}
posted @ 2026-01-20 16:16  Green&White  阅读(2)  评论(0)    收藏  举报