【JZOJ4196】二分图计数(容斥+dfs)

Problem

这里写图片描述

Hint

这里写图片描述

Solution

  看到这道题时我也想了一会容斥,只不过当时比较傻逼,想到的是 2n 2 n 暴枚一个S,再 2n 2 n 暴枚一个S1⊆S,然后就误以为会gg。。。竟然没有想到直接 O(3n) O ( 3 n ) 枚举。。。
  下面是TJ:
这里写图片描述
  这个容斥应该很好理解。当S1为空集时,S-S1=S,则S中所有点都可以任意匹配;这样可能会使某个点x与跟自己不连边的点匹配,于是又要减去;然而,若有两点x、y,一次x在S1、y在S-S1中,一次y在S1、x在S-S1中,它们都与跟自己不连边的点匹配的情况会被减两次,于是又要加回来……
  然而,这题最猥琐的是防止它重复匹配。你可以dfs出一个S、S1以后, O(n) O ( n ) 扫一遍S1里面的东西,看看它们有木有重复匹配。
  然后,你或许还会这样打来统计F(S):

int h=(l1&1?M-1:1),res=m-l1; // l1表示|S1|,res表示右边剩下多少个点
fo(i,1,l2) h=1ll*h*res--%M;// l2表示|S-S1|,h表示H(S,S1);该条语句计算S-S1中的点任意匹配的方案数
F[S]+=(ll)h;

  这样你就会得到一个这个东西:
这里写图片描述
  计算器算一下, 316=4304672141070.4sec 3 16 = 43046721 ≈ 4 ∗ 10 7 ≈ 0.4 s e c ,如果在乘个16,显然吃不消。
  于是,必须要优化这两个操作:1)防止重复匹配;2)统计F(S)。
  对于1),有两种方法:a)预先dfs出合法的S1的集合;b)我的方法,将a数组(记录左边每个点不与右边哪个点相连)离散化,dfsS和S1时统计一下出现的ai的状态,不使某个ai重复出现即可。
  对于2),我们研究一下上面的代码,发现它仅与l1、l2有关。于是预处理出一个数组s[i][j],表示当l1=i、l2=j时,H(S,S1)的值。这样便可以 O(1) O ( 1 ) 搞出。
  时间复杂度: O(n2+3nF(S)+2n) O ( n 2 ( 预 处 理 ) + 3 n ( 计 算 F ( S ) ) + 2 n ( 统 计 答 案 ) )

Code

#include <cstdio>
#include <algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;

const int N=17,M=1e9+7;
int i,j,n,m,a[N],l1,s1[N],l2,b[N],nb,a1[N],s[N][N],res,ans; 
ll F[1<<16];

void dfs(int x,int S,int S1)
{
    if(x==n)
    {
        int h=(l1&1?M-1:1); 
        F[S]=(F[S]+1ll*h*s[l1][l2])%M;
        return;
    }

    dfs(x+1,S,S1);

    int ax=1<<a1[x];
    if(!(S1&ax))
    {
        s1[++l1]=x; 
        dfs(x+1,S+(1<<x),S1+ax);
        l1--;  
    }

    l2++;
    dfs(x+1,S+(1<<x),S1);
    l2--;
}

int main()
{
    freopen("bipartite.in","r",stdin);
    freopen("bipartite.out","w",stdout);
    scanf("%d%d",&n,&m);
    fo(i,0,n-1) scanf("%d",&a[i]), b[i]=a[i];
    sort(b,b+n); nb=unique(b,b+n)-b-1;
    fo(i,0,n-1)
        fo(j,0,nb)
            if(a[i]==b[j]){a1[i]=j; break;}

    fo(i,0,n)
    {
        s[i][0]=1; res=m-i;
        fo(j,1,n) s[i][j]=1ll*s[i][j-1]*res--%M;
    }

    dfs(0,0,0);

    fo(i,1,(1<<n)-1) ans=(ll)(ans+1ll*i*F[i])%M;
    printf("%d",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值