bzoj 3812 主旋律
这题好难啊 为什么dp都这么难 计数问题 好像完全不会啊
这是wc2015 陈老师讲的题
我们可以先考虑把这个点集拆成多个强连通分量 如果强连通分量的数量大于2 那么这一个生成子图对答案是没有贡献的
对于两个强连通分量从 拆出来的那一个 向其他的连边 是不会把两个强连通分量缩在一起的
考虑容斥?? 复杂度貌似有点高 要枚举每一个强连通分量
极强无比的做法::
- f[S]表示点集S的生成子图强联通的方案数
- g[S]表示点集S的生成子图G中,若G的所有联通块都强联通,则G对g[S]存在一个贡献
- 如果G中有奇数个连通块,则对g[S]的贡献为+1,否则为-1
- h[S]表示点集S的诱导子图中有多少条边
- f[S]=2^h[S]-Σ[T是S的非空子集]2^cnt*g[T]
- 其中cnt=|{x->y|x∈S,y∈S-T}|
- (注意此时的g[S]不包含整个S强联通的情况)
lef 即S-T
h[i^j]+w[j] 即cnt
(f[i]+=mod-pows[h[i^j]+w[j]]*g[j]%mod)%=mod;
for(int j=lef;j;(--j)&=lef)
(g[i]+=mod-f[i^j]*g[j]%mod)%=mod;
核心代码以给出
可是我总觉得这一类dp题 再出来我好像还是不太会做 开始怀疑做dp题看题解的意义
1 #include <bits/stdc++.h> 2 #define breaks printf("!") 3 #define ll long long 4 #define mod 1000000007 5 using namespace std; 6 7 int n,m,num[1<<8]; 8 int cd[1<<15],rd[1<<15]; 9 ll f[1<<15],g[1<<15],h[1<<15],pows[16*16]; 10 inline Counts(int x){return num[x>>8]+num[x&255];} 11 int main() 12 { 13 scanf("%d%d",&n,&m); 14 for(int i=1;i<(1<<8);i++) 15 num[i]=num[i>>1]+(i&1); 16 pows[0]=1;for(int i=1;i<=m;i++) pows[i]=(pows[i-1]<<1)%mod; 17 for(int i=1;i<=m;i++) 18 { 19 int x,y; scanf("%d%d",&x,&y); 20 cd[1<<(x-1)]|=1<<(y-1); rd[1<<(y-1)]|=1<<(x-1); 21 } 22 //for(int i=1;i<=m;i++) printf("%d ",num[i]); printf("\n"); 23 for(int i=1;i<1<<n;i++) 24 { 25 int one=i&-i,lef=i^one; 26 h[i]=h[lef]+Counts(rd[one]&lef)+Counts(cd[one]&lef); 27 for(int j=lef;j;(--j)&=lef) 28 (g[i]+=mod-f[i^j]*g[j]%mod)%=mod; 29 static int w[1<<15]; 30 f[i]=pows[h[i]]; 31 //breaks; 32 for(int j=i;j;(--j)&=i) 33 { 34 if(j==i) w[j]=0; 35 else 36 { 37 int tmp=(i^j)&-(i^j); 38 w[j]=w[j^tmp]-Counts((i^j)&rd[tmp])+Counts(j&cd[tmp]); 39 } 40 (f[i]+=mod-pows[h[i^j]+w[j]]*g[j]%mod)%=mod; 41 } 42 g[i]+=f[i]; g[i]%=mod; 43 } 44 printf("%lld\n",f[(1<<n)-1]); 45 return 0; 46 }