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)\)。

浙公网安备 33010602011771号