UOJ#37.【清华集训2014】主旋律 状压DP

时空隧道


昨天考试第二题与此题相似….(话说这还是NOIP模拟赛???),所以才找了此题….一开始没意识到是集训…等我发现的时候…2333….还是看了题解…..


分析:
其实此题简化一下就是说有多少个生成子图是强连通的….
(所谓生成子图就是只能去边不能去点的子图)一看数据范围,妥妥的状压DP…
f[i]代表i点集的强连通生成子图数量…
由于求强连通生成子图略麻烦(略??…)所以我们转化为求非强连通的生成子图的数量….
我们如果把一个非强连通的有向图缩一缩点…一定存在一些入度为0的点…所以我们只要枚举这些点就好(以为可能会有重复的…那就通过容斥原理来解决)
M[i]解释起来略麻烦…
假设i={1,2,3}…
M[i]=cnt[{1},{2},{3}]-cnt[{1,2},{3}]-cnt[{1},{2,3}]-cnt[{1,3},{2}]+cnt[{1,2,3}](+-就是容斥原理)
cnt[{1},{2},{3}]代表的是123分别是缩完点之后入度为0的点…那么cnt[{1},{2},{3}]就代表当前状态的子图的数量…
G[i]代表的是i的边集的数量….
然后计算的时候我们只需要计算这些入度为0的点向其他点的连边….


代码如下:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
//by NeighThorn
using namespace std;
const int MOD=1000000007;
int n,m,in[1<<15],out[1<<15],num[1<<15],cnt[1<<15];//num[i]代表i集合有几个点,cnt[i]代表入度为0的强连通分量到剩下的节点的边数 
long long f[1<<15],M[1<<15],G[1<<15];//f[i]代表i的生成子图是强连通的方案数,M[i]枚举的是i缩点之后的DAG中的入度为0的强连通分量,G[i]代表的是i的边集的数量 
long long power[15*15];
signed main(void){
    scanf("%d%d",&n,&m);power[0]=1;
    for(int i=1;i<=n*n;i++)
        power[i]=power[i-1]*2%MOD;
    for(int i=1;i<1<<n;i++)
        num[i]=num[i-(i&-i)]+1;
    for(int i=1,x,y;i<=m;i++)
        scanf("%d%d",&x,&y),out[1<<x-1]|=1<<y-1,in[1<<y-1]|=1<<x-1;
    f[0]=1,M[0]=MOD-1,G[0]=1;
    for(int i=1;i<1<<n;i++){
        if(num[i]==1){
            f[i]=M[i]=G[i]=1;
            continue;
        }
        int x=i&-i,v=i-x,edgenum=0;
        for(int j=0;j<n;j++)
            if(i&(1<<j))
                edgenum+=num[out[1<<j]&i];
//cout<<edgenum<<endl;
        G[i]=f[i]=power[edgenum],M[i]=0;//f[i]=所有的子图-不合法的子图 
        for(int j=v;;j=(j-1)&v){
            int now=j|x;
            if(now!=i)
                M[i]=(M[i]-f[now]*M[i-now]%MOD+MOD)%MOD;//容斥原理,f[now]本身就是一个强连通图,M[i-now]就是一个子问题,因为多了now这个强连通分量奇偶性改变所以- 
            if(j==0)
                break;
        }   
        int sum=0;//不合法的
        for(int j=i;j;j=(j-1)&i){
            if(j==i)
                cnt[j]=0; 
            else{
                int lala=(i-j)&(-(i-j)),lalala=j|lala;
                cnt[j]=cnt[lalala]-num[out[lala]&(i-lalala)]+num[in[lala]&j];
            }
            sum=(sum+M[j]*G[i-j]%MOD*power[cnt[j]]%MOD)%MOD;
        }
        f[i]=(f[i]-sum+MOD)%MOD,M[i]=(M[i]+f[i])%MOD;
    }
    cout<<f[(1<<n)-1]<<endl;
    return 0;
}

by >_< NeighThorn

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值