洛谷 P10104 [GDKOI2023 提高组] 异或图

题目链接

若图中没有边,则是这道题,可以做到 \(\text O (n \log V)\),具体参见上面链接。

有边的情况可以容斥,先把不等关系转成相等关系,若连一条相等关系的边则乘上 \(-1\) 的容斥系数。

\(h _ S\) 为只考虑 \(S\) 集合内的边的容斥系数之和,显然 \(h _ S\)\(S\) 内是否有边只有 \(0 / 1\) 两种取值。再记 \(f _ S\) 为只考虑 \(S\) 集合内的边并使点集 \(S\) 连通的容斥系数之和,这可以单次容斥掉不连通的情况,枚举 \(S\) 中编号最小的点所在的连通块计算。

回到原问题,可以先将 \(a _ i\) 从小到大排序,考虑选了一个边集之后的连通情况,对于所有连通块的 \(a _ i\) 最小值,若连通块大小为偶数则答案乘上 \((a _ i + 1)\),然后把所有奇数大小连通块通过没有边的情况的方式计算。

接着考虑用一个 dp 来刻画上述流程。记状态 \(g _ {i, S}\) 表示做到 \(i\),还剩下的数的集合为 \(S\) 的容斥系数之和,到 \(i\) 时枚举删去 \(i \sim n\) 的子集 \(T\),转移就分连通块大小的奇偶性分讨即可。这部分时间复杂度是等比数列求和 \(\text O (3 ^ n)\)

答案为 \(\sum _ S g _ {n, S} \times S\) 集合内部没有边的方案数。时间复杂度 \(\text O (3 ^ n + 2 ^ n n \log V)\)

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 20
#define PN ((1<<15)+5)
#define clz __builtin_clz
#define pc __builtin_popcount
using namespace std;

typedef long long ll;
const int k=60,mod=998244353;
int n,m,ans,p[N],c[PN],h[PN],f[PN],g[N][PN];
ll t,a[N],d[N];
bool G[N][N];
struct node {
	ll x; int id;
	bool operator<(node a) {return x<a.x;}
} b[N];
inline void add(int &x,ll y) {x=(x+y)%mod;}
namespace sub {
	int f[N][2];
	int solve(int n,ll a[N]) {
		int ans=0;
		for(int i=k-1;~i;i--) {
			int ch=t>>i&1,c=0,cc=0,res=0;
			for(int j=1;j<=n;j++) c+=a[j]>>i&1;
			memset(f,0,sizeof(f)),f[0][0]=1;
			for(int j=1;j<=n;j++) {
				int w=((a[j]&(1ll<<i)-1)+1)%mod; res=1ll*res*w%mod;
				if(a[j]>>i&1) {
					cc++,add(res,f[j-1][(c-cc)&1^ch]);
				}
				for(int u=0;u<2;u++) {
					if(a[j]>>i&1) {
						add(f[j][u],f[j-1][u]*((1ll<<i)%mod));
						add(f[j][u],1ll*f[j-1][!u]*w);
					} else {
						add(f[j][u],1ll*f[j-1][u]*w);
					}
				}
			}
			add(ans,res);
			if((c&1)!=ch) return ans;
		}
		return (ans+1)%mod;
	}
}
int main() {
	scanf("%d%d%lld",&n,&m,&t);
	for(int i=1;i<=n;i++) scanf("%lld",&b[i].x),b[i].id=i;
	sort(b+1,b+n+1);
	for(int i=1;i<=n;i++) a[i]=b[i].x,p[b[i].id]=i;
	for(int i=1,x,y;i<=m;i++) {
		scanf("%d%d",&x,&y),x=p[x],y=p[y],G[x][y]=G[y][x]=1;
		for(int j=1;j<1<<n;j++) if(j>>x-1&1&&j>>y-1&1) c[j]++;
	}
	for(int i=1;i<1<<n;i++) h[i]=!c[i];
	for(int i=1;i<1<<n;i++) {
		f[i]=h[i];
		int bit=1<<(31^clz(i));
		for(int j=(i-1)&i;j&bit;j=j-1&i) add(f[i],-1ll*f[j]*h[i^j]);
		f[i]=(f[i]+mod)%mod;
	}
	g[0][(1<<n)-1]=1;
	for(int i=0;i<n;i++) {
		int sti=((1<<n-i)-1)<<i;
		for(int j=0;j<1<<n;j++) if(g[i][j]) {
			if(j>>i&1) {
				int op=j&sti;
				for(int k=op;;k=k-1&op) if(~pc(k)&1) {
					if(k>>i&1)
						add(g[i+1][j^k],1ll*g[i][j]*f[k]%mod*((a[i+1]+1)%mod));
					else
						add(g[i+1][j^k],1ll*g[i][j]*f[k|1<<i]);
					if(!k) break;
				}
			} else {
				add(g[i+1][j],g[i][j]);
			}
		}
	}
	for(int i=0;i<1<<n;i++) {
		int l=0;
		for(int j=1;j<=n;j++) if(i>>j-1&1) d[++l]=a[j];
		add(ans,1ll*g[n][i]*sub::solve(l,d));
	}
	printf("%d\n",ans);
	return 0;
}
posted @ 2026-01-12 07:53  yemuzhe  阅读(11)  评论(0)    收藏  举报