BZOJ3812: 主旋律

138 篇文章 0 订阅
11 篇文章 0 订阅

cls在wc讲的神题,无限Orz

直接求原图强联通的生成子图个数不好求,考虑用总方案数减不是强联通的方案数
如果原图不是强联通,缩点后一定是一个点数>1的DAG
有一个比较暴力的做法,即枚举每个强联通分量,将它缩点
f[S]SDAG,cnt[S]S
由于一个DAG一定有出度为0的点为汇,我们可以枚举汇集 T
ways(S,T)ST
f[S]=TSf[ST]2ways(ST,T)

但是这样做是不对的…

比如汇集是点A,B的情况,我们枚举A时会算到这种情况,枚举B时也会算到这种情况…..
考虑容斥,因为对于集合S,有 TS,Tϕ(1)|T|1=1 (集合容斥,和普通的容斥其实是一个东西)
于是我们就得到了正确的dp式
f[S]=TS(1)|T|1f(ST)2ways(ST,T)
但是这样做复杂度太高了因为要枚举所有可能的强联通情况
事实上对于同一个点集 T 包含的点,枚举强联通只是为了统计(1)|T|1
而这个东西我们可以直接dp统计

g[T]T
f[S]S
转移g时固定一个点u,枚举包含u的强联通块

g[S]=f[S]TS,uT,Tϕf(T)g(ST)
(奇偶性变化所以带负号)
转移f时,枚举出度为0的强联通汇集包含的点集T
f[S]=2cnt[S]TS,Tϕg(T)2cnt[ST]+ways(ST,T)

(这里当会发现f[S]和g[S]会互相调用,但事实上f[S]的dp式中当T=S时,g[T]不包含f[S]的情况因为若包含了计算的就不是DAG而直接是强联通了)

然后就可以 O(3n)

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
#define lowbit(x) x&(-x)
using namespace std;

const int maxn = 15;
const int mask2 = 1<<15;
const int mask3 = 14348907;
const int mod = 1e9+7;
inline void add(int &a,const int &b){a+=b;if(a>=mod)a-=mod;if(a<0)a+=mod;}

int pw2[maxn*maxn],pw3[maxn];
int to3[mask2];
int sum[mask2],bac[mask2];

void pre()
{
    pw2[0]=1,pw3[0]=1;
    for(int i=1;i<maxn*maxn;i++) pw2[i]=(pw2[i-1]<<1)%mod;
    for(int i=1;i<maxn;i++) pw3[i]=pw3[i-1]*3;
    for(int i=1;i<mask2;i++) sum[i]=sum[i^lowbit(i)]+1;
    for(int i=0;i<maxn;i++) bac[1<<i]=i+1;
    to3[0]=0;
    for(int i=1;i<mask2;i++) to3[i]=to3[i^lowbit(i)]+pw3[bac[lowbit(i)]-1];
}
int n,m,al;
int outd[maxn],ind[maxn],cnt[mask2];
int ways[mask3];
void dpway()
{
    for(int i=1;i<al;i++)
    {
        int lb=lowbit(i),x=bac[lb];
        cnt[i]=cnt[i^lb]+sum[outd[x]&i]+sum[ind[x]&i];
    }
    for(int i=1;i<al;i++) for(int j=(i-1)&i;j;j=(j-1)&i)
    {
        int u=i^j,v=j,lb=lowbit(v),x=bac[lb];
        ways[to3[u]+to3[v]*2]=ways[to3[u]+to3[v^lb]*2]+sum[ind[x]&u];
    }
}
// connected  Divide
int f[mask2],g[mask2];
void dp()
{
    g[0]=-1;
    for(int i=1;i<al;i++)
    {
        int x=bac[lowbit(i)];
        for(int j=(i-1)&i;j;j=(j-1)&i) if(j>>x-1&1)
            add(g[i],-(ll)f[j]*g[i^j]%mod);
        f[i]=pw2[cnt[i]];
        for(int j=i;j;j=(j-1)&i)
            add(f[i],-(ll)g[j]*pw2[cnt[i^j]+ways[to3[i^j]+to3[j]*2]]%mod);
        add(g[i],f[i]);
    }
}

int main()
{
    pre();
    scanf("%d%d",&n,&m); al=1<<n;
    for(int i=1;i<=m;i++)
    {
        int x,y; scanf("%d%d",&x,&y);
        outd[x]|=1<<y-1;
        ind[y]|=1<<x-1;
    }
    dpway();
    dp();
    int ans=f[al-1]; add(ans,0);
    printf("%d\n",ans);

    return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值