20260314模拟赛

20260314模拟赛

别笑,你试也过不了第二关

\(f_{S,i}\) 表示以 \(i\) 为根,点集为 \(S\) 的仙人掌方案数。

\(g_{S,i}\) 表示以 \(i\) 为根,点集为 \(S\)\(i\) 只在一个环上的仙人掌方案数。

\(h_{S,x,y}\) 表示以 \(x,y\) 为链的两端,除 \(x\) 外其余点都可能接了一些仙人掌,点集为 \(S\) 的方案数。

  • \(h_{S,x,y}\) 接上 \(f_{T,v}\) 转移到 \(h_{S+T,x,v}\)

    在这个过程中直接枚举所有信息是 \(O(3^nn^3)\) 的。但是可以将其分为两步转移,先加点再加仙人掌,即先 \(w_{S,x,y}\to p_{S+\{v\},x,v}\)\(p_{S+\{v\},x,v}\times f_{T,v}\to h_{S+T,x,v}\)

  • \(h_{S,x,y},f_{S-\{x\},y}\to g_{S,x}\)

    其中环会被算两边所以要乘 \(\frac 1 2\),但是如果只是一条边的环就只会被算一遍,所以单独多算一次一条边的方案然后都乘 \(\frac 1 2\)

  • \(g_{S,i}\) 枚举子集得到 \(f_{S,i}\)

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline int read(){
	int s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-') k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

const int N=15,mod=998244353;
int n,m,e[N];
ll f[1<<N][N],g[1<<N][N],w[1<<N][N][N],v[1<<N][N][N];

ll Mod(ll x){return x>=mod?x-mod:x;}
void Add(ll &x,ll y){x=Mod(x+y);}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n=read();m=read();
	for(int i=1;i<=m;i++){
		int u=read(),v=read();
		u--;v--;
		e[u]|=1<<v;
		e[v]|=1<<u;
	}
	int S=(1<<n)-1;
	for(int i=0;i<n;i++) g[1<<i][i]=1,w[1<<i][i][i]=1;
	for(int s=1;s<=S;s++){
		for(int x=0;x<n;x++) if(s>>x&1)
			for(int y=0;y<n;y++) if(x!=y&&s>>y&1){
				int T=s^1<<x^1<<y;
				for(int t=T;;t=(t-1)&T){
					int A=t|1<<x|1<<y,B=(s^A)|1<<y;
					(w[s][x][y]+=v[A][x][y]*f[B][y])%=mod;
					if(t==0) break;
				}
			}
		for(int x=0;x<n;x++) if(s>>x&1)
			for(int y=0;y<n;y++) if(s>>y&1)
				for(int z=0;z<n;z++) if(!(s>>z&1)&&(e[y]>>z&1))
					Add(v[s|1<<z][x][z],w[s][x][y]);
		for(int x=0;x<n;x++)
			for(int y=0;y<n;y++)
				if(w[s][x][y]&&(e[x]>>y&1))
					(g[s][x]+=(mod+1>>1)*w[s][x][y])%=mod;
		for(int x=0;x<n;x++)
			for(int y=0;y<n;y++)
				if(x!=y&&(s>>x&1)&&(s>>y&1)&&(e[x]>>y&1))
					(g[s][x]+=(mod+1>>1)*f[s^1<<x][y])%=mod;
		for(int i=0;i<n;i++) Add(f[s][i],g[s][i]);
		for(int i=0;i<n;i++)
			if(s>>i&1){
				int T=s^1<<i,rt=__lg(T&-T);
				for(int t=(T-1)&T;t>0;t=(t-1)&T){
					if(t>>rt&1) continue;
					(f[s][i]+=f[t|1<<i][i]*g[(T^t)|1<<i][i])%=mod;
				}
			}
	}
	printf("%lld",f[S][0]);
	return 0;
}

狡兔三窟

考虑怎么求 \(f(1,n)\)

\(1\sim i\) 加入每一层,若能找到不相交路径在当前层终点是子集 \(S\),称其是合法的。\(i\) 层一个 \(S\) 是合法的,当且仅当存在 \(i-1\) 层子集 \(T \subseteq N(S)\) 是合法的,而且 \(S,T\) 之间有完美匹配。

考虑 Hall 定理。称 \(T\) 是可能的当且仅当存在一个 \(Z \subseteq N(T), |Z| = |T|\)\(Z\) 是合法的。则 \(S\) 合法要求所有子集都是可能的。判断 \(T\) 是否满足条件可以高维前缀和求 \(N(T)\) 中最大合法子集大小。

原问题只需要对每个 \((i,S)\) 求出能够使其合法的最小 \(j\)

\(f_{i,S}\) 表示能够让 \((i,S)\) 合法的最小 \(j\)\(g_{i,S}\) 表示能够让 \((i,S)\) 可能的最小 \(j\)

  • \(f_{i,S} = \max_{T \subseteq S} g_{i,T}\)

  • \(g_{i,S} = \min_{T \subseteq N(S), |T| = |S|} f_{i-1,T}。\)

高维前缀 max/min 做到 \(O(k2^k)\) 转移。

总复杂度 \(O(nk^2 2^k)\)

posted @ 2026-03-20 09:36  programmingysx  阅读(4)  评论(0)    收藏  举报
Title