• [BZOJ3812]主旋律:状压DP+容斥原理


    分析

    Miskcoo orz

    (f[S])表示使得(S)这个点集强连通的方案数。

    然后呢?不会了

    考虑到将一个有向图SCC缩点后,得到的新图是一个DAG,所以我们可以类比带标号DAG计数的解法来寻找这道题的突破口。

    我们可以枚举哪些点所构成的SCC在缩点后入度为(0),然后令(g[S])表示使(S)这个点集缩点后是一堆强连通分量且之间不存在边的方案数(不然入度就不是(0)了),可以得到下面这个递推式:

    [f[S]=2^{cnt[S]}-sum_{T subseteq S,T eq emptyset}2^{cnt[S-T]+to[T][S-T]} imes g[T] ]

    这个递推式显然是错的(辣鸡博主欺骗感情),因为一个有向图SCC缩点后会出现多个入度为(0)的点。在带标号DAG计数I中,我们选择使用小学奥数容斥解决重复计数的问题,这里同样如此。但是我们发现,这里是加是减取决于度数为(1)的SCC的个数而并非是点数,因此这个容斥过程很难通过正常方法实现。

    考虑上网搜题解,我们会发现一个很神奇的操作,我们可以让容斥在计算(g[T])的过程中完成。如果不能理解的话,我们可以看一看(g)数组的递推式:

    [g[S]=f[S]+sum_{T subsetneq S,u otin T,T eq emptyset}f[S-T] imes g[T] ]

    (P.S.这里用到了一个trick,就是通过枚举编号最小的点所在SCC防止重复统计,不过这个trick貌似拯救不了之前那个错误的递推式)

    原本是长这个样子的,不过我们可以稍作修改:

    [g[S]=f[S]-sum_{T subsetneq S,u otin T,T eq emptyset}f[S-T] imes g[T] ]

    这里把加号变成了减号,含义大概就是每新加入一个SCC,符号就要变化(即取相反数)。

    然后(cnt[S])(to[S][T])数组的处理都很简单就不扯这么多了。

    好神仙啊。

    代码

    #include <bits/stdc++.h>
    #define rin(i,a,b) for(register int i=(a);i<=(b);++i)
    #define irin(i,a,b) for(register int i=(a);i>=(b);--i)
    #define trav(i,a) for(register int i=head[a];i;i=e[i].nxt)
    #define lowbit(x) ((x)&(-(x)))
    #define r (s^t)
    typedef long long LL;
    using std::cin;
    using std::cout;
    using std::endl;
    
    inline int read(){
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    
    const int MAXN=20;
    const LL MOD=1e9+7;
    
    int n,m,inE[1<<15],outE[1<<15];
    int popcnt[1<<15];
    LL f[1<<15],g[1<<15],cnt[1<<15],to[1<<15];
    LL pow2[230];
    
    int main(){
    	n=read(),m=read();
    	pow2[0]=1;
    	rin(i,1,225) pow2[i]=(pow2[i-1]<<1)%MOD;
    	rin(i,1,m){
    		int u=read()-1,v=read()-1;
    		inE[1<<v]|=(1<<u);
    		outE[1<<u]|=(1<<v);
    	}
    	rin(i,0,(1<<n)-1) popcnt[i]=__builtin_popcount(i);
    	rin(s,1,(1<<n)-1){
    		int x=lowbit(s),y=(s^x);
    		cnt[s]=cnt[y]+popcnt[inE[x]&s]+popcnt[outE[x]&s];
    		for(register int t=s;t;t=((t-1)&s)){
    			if(t==s){to[t]=0;continue;}
    			int xx=lowbit(r);
    			to[t]=to[t^xx]-popcnt[outE[xx]&r]+popcnt[inE[xx]&t];
    		}
    		for(register int t=y;t;t=((t-1)&y)) g[s]=(g[s]-f[r]*g[t]%MOD+MOD)%MOD;
    		f[s]=pow2[cnt[s]];
    		for(register int t=s;t;t=((t-1)&s)) f[s]=(f[s]-pow2[cnt[r]+to[t]]*g[t]%MOD+MOD)%MOD;
    		g[s]=(g[s]+f[s])%MOD;
    	}
    	printf("%lld
    ",f[(1<<n)-1]);
    	return 0;
    }
    
  • 相关阅读:
    角点检测
    25岁董事长给大学生的18条忠告
    10大忠告
    实验常用正交表
    面试必备15题
    专家系统
    深圳租房完全攻略
    你应选什么样的职业?
    托福报名详细过程解说
    Managed Direct3D开发经验浅析
  • 原文地址:https://www.cnblogs.com/ErkkiErkko/p/10410239.html
Copyright © 2020-2023  润新知