题目链接:
题目大意:给n个点的一个无向图,点有点权,要求将这n个点划分成若干个部分,每部分合法当且仅当这部分中所有点之间的边不能构成欧拉回路。对于一种划分方案,第i个部分的权值为这一部分中所有点的权值和比上前i部分所有点的权值和的p次方,一种划分方案的权值为每部分的权值之积。要求求出所有划分方案的权值之和。
我们设f[S]为选中点的状态集合为S时的答案(其中S为二进制状态),设T为S集合最后一次划分出的集合且要保证集合T合法,那么可以得到转移方程(其中sum代表集合中点权和):
$f[S]=sumlimits_{Tsubseteq S}^{ }f[S-T]*(frac{sum[T]}{sum[S]})^p$
这个子集DP直接枚举子集的时间复杂度是O(3^n),显然过不去,但我们发现这个DP相当于枚举两个集合i,j满足$icap j= varnothing ,icup j=S$
这个如果只有子集并的条件可以用直接用FWT来优化,但还要求交集为空的条件就不能一维DP优化了。
我们假设一个点能被划分到多个部分中,那么DP状态就变成了二维:f[i][S]表示选取点集合为S,每部分包含的点数和为i的答案。
设g[i][S]表示集合为S,选取点数为i时sum[S]的p次方,如果S不合法或|S|!=i,那么g[i][S]就为0。(其中|S|表示S集合中的点数即二进制状态中1的个数)
那么转移方程就变成了:
$f[i][S]=sumlimits_{j=1}^{i}sumlimits_{Tsubseteq S}^{ }frac{f[j][S-T]*g[i-j][T]}{sum[S]^p}$
这样我们对f数组和g数组进行FWT转化成子集和表达式然后DP,每次乘上sum[S]^p的逆元即可,最后的答案为f[|U|][U],其中U为全集。时间复杂度为O(2^n*n^2)。
再来说一下如何判欧拉回路:
这个很简单只要一个图联通且每个点的度数都是偶数,那么这个图就是欧拉回路,对于每个二进制状态预处理判断即可,用bfs或dfs或并查集判断都可以。预处理时间复杂度同样是O(2^n*n^2)。
#include<set> #include<map> #include<queue> #include<cmath> #include<stack> #include<cstdio> #include<vector> #include<bitset> #include<cstring> #include<iostream> #include<algorithm> using namespace std; int f[22][2100000]; int g[22][2100000]; int n,m,p; const int mod=998244353; int x[10000]; int y[10000]; int s[30]; int fa[30]; int v[22]; int w[2100000]; int cnt; int mask; int lg[2100000]; int inv[2100000]; inline int find(int x) { if(fa[x]==x) { return x; } return fa[x]=find(fa[x]); } inline void FWT(int *f,int opt) { for(int k=2;k<=(1<<n);k<<=1) { for(int i=0,t=k>>1;i<(1<<n);i+=k) { for(int j=i;j<i+t;j++) { if(opt==1) { f[j+t]=(f[j+t]+f[j])%mod; } else { f[j+t]=(f[j+t]-f[j]+mod)%mod; } } } } } inline int quick_pow(int x,int y) { int res=1; while(y) { if(y&1) { res=1ll*res*x%mod; } y>>=1; x=1ll*x*x%mod; } return res; } int main() { scanf("%d%d%d",&n,&m,&p); for(int i=1;i<=m;i++) { scanf("%d%d",&x[i],&y[i]); } for(int i=1;i<=n;i++) { scanf("%d",&v[i]); } mask=(1<<n)-1; for(int i=1;i<=n;i++) { lg[1<<(i-1)]=i; } for(int i=1;i<=mask;i++) { int sum=0; for(int j=0;j<n;j++) { if(i&(1<<j)) { w[i]+=v[j+1]; sum++; } } int num=sum; inv[i]=quick_pow(w[i],mod-2); for(int j=1;j<=n;j++) { fa[j]=j; s[j]=0; } for(int j=1;j<=m;j++) { if(((1<<(x[j]-1))&i)&&((1<<(y[j]-1))&i)) { int u=find(x[j]); int v=find(y[j]); if(u!=v) { sum--; fa[u]=v; } s[x[j]]++; s[y[j]]++; } } int flag=0; for(int j=0;j<n;j++) { if((1<<j)&i) { flag|=(s[j+1]&1); } } if(flag||sum>1) { g[num][i]=quick_pow(w[i],p); } } f[0][0]=1; FWT(f[0],1); for(int i=0;i<=n;i++) { FWT(g[i],1); } for(int i=1;i<=n;i++) { for(int j=1;j<=i;j++) { for(int k=0;k<=mask;k++) { f[i][k]+=1ll*g[j][k]*f[i-j][k]%mod; f[i][k]%=mod; } } FWT(f[i],-1); for(int k=0;k<=mask;k++) { f[i][k]=1ll*f[i][k]*quick_pow(inv[k],p)%mod; } if(i<n) { FWT(f[i],1); } } printf("%d",f[n][mask]); }