专栏文章

题解:P11834 [省选联考 2025] 岁月

P11834题解参与者 2已保存评论 3

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
3 条
当前快照
1 份
快照标识符
@minqfcvq
此快照首次捕获于
2025/12/02 06:40
3 个月前
此快照最后确认于
2025/12/02 06:40
3 个月前
查看原文
为了方便,用 +- 来表示集合的加减。
numsnum_s 表示 ss 内部的边数,cross(s,t)cross(s,t) 表示 ss 指向 tt 的边数,因为最开始是无向边,有 corss(s,t)=nums+tnumsnumt2corss(s,t)=\frac{num_{s+t}-num_s-num_t}{2}
转为数方案数,把无向边当作两条有向边,求有多少种删边方案合法。

数合法的外向生成树

对于 C 性质,边权全相等,合法的外向生成树就是合法的最小生成树。
要存在合法的外向生成树,即存在一些根能去到所有点,那缩点后这些根应当构成一个强连通分量,且这个强连通分量是唯一的 00 入度的强连通分量。
数强连通分量即 主旋律
fsf_s 表示只考虑 ss 中的边且 ss 为强连通分量的方案数。容斥为减去不合法。不合法的方案是缩点后至少有 cnt2cnt\ge 2 零度点,有 1cnt+1-1^{cnt+1} 的容斥系数,剩下的可以乱连。再设 gsg_s 表示 ss 是若干个相互之间没有边的强连通分量带上容斥系数并起来的方案数,每次可以拼上一个包含最小点的强连通分量。
fs=2numstsgt2numst+cross(t,st)f_s=2^{num_s}-\sum_{t\subseteq s} g_t2^{num_{s-t}+cross(t,s-t)}
gs=fs+ts,lowbit(s)=lowbit(t)ftgstg_s=f_s+\sum_{t\subset s,lowbit(s)=lowbit(t)} -f_tg_{s-t}
先算 gsg_s,再算 fsf_s,再给 gsg_s 加上 fsf_s
至于求答案。设 anssans_sss 是全图缩点后的唯一 00 入度强连通分量,也就是 ss 是根的方案数。钦定若干个的 00 入度点强连通分量 ss,再枚举真正的 00 入度点强连通分量 tst\subseteq s,剩下的乱连。那些钦定的假的 00 入度点强连通分量 sts-t 就是 gstg_{s-t},再拼上 tt 还要带 1-1 的系数。
anst=tsftgst2numUs+cross(s,Us)ans_t=\sum_{t\subseteq s}-f_tg_{s-t}2^{num_{U-s}+cross(s,U-s)}

code

CPP
int n,m,ans;
int e[maxn],num[1<<maxn];
int f[1<<maxn],g[1<<maxn];
int pw[maxn*maxn];
void work(){
	n=read();m=read();ans=0;
	for(int i=0;i<n;i++)e[i]=0;
	for(int s=0;s<(1<<n);s++)f[s]=g[s]=num[s]=0;
	for(int i=1;i<=m;i++){
		int u=read()-1,v=read()-1,w=read();
		e[u]|=1<<v,e[v]|=1<<u;
		num[(1<<u)|(1<<v)]+=2;
	}
	for(int i=0;i<n;i++){
		for(int s=0;s<(1<<n);s++)if(s&(1<<i))num[s]+=num[s^(1<<i)];
	}
	f[0]=1,g[0]=mod-1;
	for(int s=1;s<(1<<n);s++){
		for(int t=(s-1)&s;t;t=(t-1)&s)if(__lg(s)==__lg(t))(g[s]+=mod-f[t]*g[s^t]%mod)%=mod;
		f[s]=pw[num[s]];
		for(int t=s;t;t=(t-1)&s){
			int cnt=(num[s]-num[t]-num[s^t])/2+num[s^t];
			(f[s]+=mod-g[t]*pw[cnt]%mod)%=mod;
		}
		(g[s]+=f[s])%=mod;
	}
	for(int s=1;s<(1<<n);s++){
		int cnt=(2*m-num[s]-num[((1<<n)-1)^s])/2+num[((1<<n)-1)^s];
		for(int t=s;t;t=(t-1)&s){
			(ans+=mod-f[t]*g[s^t]%mod*pw[cnt]%mod)%=mod;
		}
	}
	printf("%lld\n",ans*ksm(ksm(4,m))%mod);
}

数合法的最小外向生成树

要求 最小外向生成树 权值等于原图 最小生成树 权值。
对于最小的限制,就是要求对于任意一个权值 WW,只加入 wWw\le W 的边 (u,v)(u,v),合法的 最小外向生成树 的连通性要等于 最小生成树 的连通性。
于是从小到大按边权加入边,只考虑能使原来连通块合并的新边。这些新边权值全部相同,可以把之前连通块的根当作点,求选一些新边使这些点连通的方案数。
belsbel_s 表示 ss 这些点在旧图中能去到的极大连通块的并,coefscoef_s 表示旧图中 ss 分别是 belsbel_s 中的根的方案数。对于 ss,加入新边的情况下,只考虑 belsbel_s 内的边,anssans_s 表示在ss 是合并后连通块的根的方案数;fsf_s 表示ss 是强连通分量的方案数;gsg_s 表示 ss 是若干个强连通分量带上容斥系数拼起来的方案数。
对于两个不同连通块的可能的根 s,ts,t,且要求 ss 不能指向 tt,则 belsbel_s 不能指向 tt,此时 ss 指向 belttbel_t-ttt 指向 belsbel_sbelssbel_s-sbelttbel_t-t 之间的边是可以存在的。
fsf_s 时,总方案数是 coefs2numbelscoef_s2^{num_{bel_s}},减去 tt 是若干个 0 度强连通分量的 gtg_t,剩下的 sts-tcoefstcoef_{s-t},且 sts-t 不能指向 tt 。算 gsg_s 时,还是枚举包含最小点的 tt,且 ttsts-t 之间不能有边。算 anstans_t 时,还要额外计算整个新连通块剩下部分的边。
fs=coefs2numbelsts,beltbelst=gtcoefst2cross(t,belst)+numbelstnumbelttf_s=coef_s2^{num_bel_s}-\sum_{t\subseteq s,bel_t\cap bel_{s-t}=\empty}g_tcoef_{s-t}2^{cross(t,bel_{s-t})+num_{bel_{s-t}}-num_{bel_t-t}}
gs=fsts,lowbit(s)=lowbit(t),beltbelst=ftgst2cross(t,belst(st))+cross(st,beltt)+numbelssnumbelttnumbelst(st)g_s=f_s-\sum_{t\subset s,lowbit(s)=lowbit(t),bel_t\cap bel_{s-t}=\empty}f_tg_{s-t}2^{cross(t,bel_{s-t}-(s-t))+cross(s-t,bel_t-t)+num_{bel_s-s}-num_{bel_t-t}-num_{bel_{s-t}-(s-t)}}
anstans_t 系数和 gsg_s 大体一样。
只有连通块合并时枚举子集,更小连通块做的次数加起来应该没有全局做的多,复杂度 O(3n)O(3^n)

code

CPP
int n,m,res,mul=1;
int e[maxn],num[1<<maxn];
int f[1<<maxn],g[1<<maxn],coef[1<<maxn];
int pw[maxn*maxn];
int fa[maxn],id[maxn],vis[maxn];
int fd(int x){
	if(fa[x]==x)return x;
	return fa[x]=fd(fa[x]);
}
tuple<int,int,int> edge[maxn*maxn];
int ans[1<<maxn];
int bel[1<<maxn],sum[1<<maxn];
int cross(int s,int t){return (num[s|t]-num[s]-num[t])/2;}
void work(){
	n=read();m=read();mul=1;
	for(int i=1;i<=m;i++){
		int u=read()-1,v=read()-1,w=read();
		edge[i]={w,u,v};
	}
	for(int s=0;s<(1<<n);s++)ans[s]=0;
	for(int i=0;i<n;i++)ans[1<<i]=1;
	sort(edge+1,edge+m+1);
	for(int i=0;i<n;i++)fa[i]=i,id[i]=1<<i;
	for(int l=1,r=1;l<=m;l=r+1){
		while(r+1<=m&&get<0>(edge[l])==get<0>(edge[r+1]))r++;
		bool fl=0;
		for(int i=l;i<=r;i++){
			auto[w,u,v]=edge[i];
			if(fd(u)!=fd(v))fl=1;
			else mul=mul*4%mod;
		}
		if(!fl)continue;
		for(int i=0;i<n;i++)e[i]=0;
		for(int s=0;s<(1<<n);s++)num[s]=0;
		for(int i=l;i<=r;i++){
			auto[w,u,v]=edge[i];
			if(fd(u)!=fd(v)){
				e[u]|=1<<v,e[v]|=1<<u;
				num[(1<<u)|(1<<v)]+=2;
			}
		}
		for(int i=0;i<n;i++){
			for(int s=0;s<(1<<n);s++)if(s&(1<<i))num[s]+=num[s^(1<<i)];
		}
		for(int s=0;s<(1<<n);s++)sum[s]=0;
		for(int i=0;i<n;i++)if(fa[i]==i){
			for(int s=id[i];s;s=(s-1)&id[i])bel[s]=id[i],(sum[id[i]]+=ans[s])%=mod;
		}
		sum[0]=coef[0]=1;
		for(int s=1;s<(1<<n);s++){
			int u=__lg(s);
			bel[s]=bel[1<<u]|bel[s^(1<<u)];
			if((s&bel[1<<u])==bel[1<<u])sum[s]=sum[s^bel[1<<u]]*sum[bel[1<<u]]%mod;
			else sum[s]=0;
			coef[s]=coef[s^(s&bel[1<<u])]*ans[s&(bel[1<<u])]%mod;
		}
		for(int i=0;i<n;i++)vis[i]=0;
		for(int i=l;i<=r;i++){
			auto[w,u,v]=edge[i];
			u=fd(u),v=fd(v);
			if(u!=v)fa[u]=v,vis[v]=1,id[v]|=id[u],id[u]=0;
		}
		for(int i=0;i<n;i++)if(fa[i]==i&&vis[i]){
			vector<int> ids;
			for(int s=id[i];s;s=(s-1)&id[i])ids.pb(s);
			reverse(ids.begin(),ids.end());
			f[0]=1,g[0]=mod-1;
			for(int s:ids){
				g[s]=0;for(int t=(s-1)&s;t;t=(t-1)&s)if(!(bel[t]&bel[s^t])&&__lg(s)==__lg(t)){
					int cnt=cross(t,bel[s^t]^(s^t))+cross(s^t,bel[t]^t)+num[bel[s]^s]-num[bel[t]^t]-num[bel[s^t]^(s^t)];
					(g[s]+=mod-f[t]*g[s^t]%mod*pw[cnt]%mod)%=mod;
				}
				f[s]=coef[s]*pw[num[bel[s]]]%mod;
				for(int t=s;t;t=(t-1)&s)if(!(bel[t]&bel[s^t])){
					int cnt=cross(t,bel[s^t])+num[bel[s]^t]-num[bel[t]^t];
					(f[s]+=mod-g[t]*coef[s^t]%mod*pw[cnt]%mod)%=mod;
				}
				(g[s]+=f[s])%=mod;
			}
			for(int s:ids)ans[s]=0;
			for(int s:ids){
				for(int t=s;t;t=(t-1)&s)if(!(bel[t]&bel[s^t])){
					int cnt=cross(t,id[i]^bel[t]^(s^t))+cross(s^t,id[i]^t^bel[s^t])+num[id[i]^s]-num[bel[t]^t]-num[bel[s^t]^(s^t)];
					(ans[t]+=mod-f[t]*g[s^t]%mod*pw[cnt]%mod*sum[id[i]^bel[s]]%mod)%=mod;
				}
			}
		}
	// for(int s=0;s<(1<<n);s++)cout<<f[s]*mul%mod<<" ";cout<<"\n";
	// for(int s=0;s<(1<<n);s++)cout<<g[s]*mul%mod<<" ";cout<<"\n";
	// for(int s=0;s<(1<<n);s++)cout<<ans[s]*mul%mod<<" ";cout<<"\n";
	}
	res=0;for(int s=0;s<(1<<n);s++)(res+=ans[s])%=mod;
	printf("%lld\n",res*mul%mod*ksm(ksm(4,m))%mod);
}

评论

3 条评论,欢迎与作者交流。

正在加载评论...