bzoj3812: 主旋律 dp+容斥

24 篇文章 0 订阅
7 篇文章 0 订阅

题意

给你有向图,问有多少个强联通子图

分析

经典的计数问题啊,做第二遍了

fs=2sts,t2cntst×2ways(st,t)gt f s = 2 s − ∑ t ⊆ s , t ≠ ∅ 2 c n t s − t × 2 w a y s ( s − t , t ) g t

首先枚举入度为0的强联通内的点,然后进行容斥
gs=fsts,t,utftgst g s = f s − ∑ t ⊆ s , t ≠ ∅ , u ∈ t f t g s − t

这里的容斥,简单解释一下,假设我们的图不是强联通的,那么缩点之后肯定是一个DAG图

对于一个DAG图,枚举入度为0的点,假设两个点u,v都是入度为0的图,那么枚举到u的时候,v会被算一次,枚举v的时候,u会被算一次,这样奇数次就是-1,偶数次就是+1

对于一个强联通分量,我们枚举缩点后入度为0的强联通分量,(当然了,s-t中可能也有入度为0的强联通分量,才需要容斥)然后奇数个就-1,偶数个就+1

gs g s 就是表示,对于s集合里面的点,分成强联通分量,其实就是上诉说的容斥

对于t=s的时候, gs g s 不用加上 fs f s ,因为这个时候已经是整个子集强联通的

然后就是 O(3n) O ( 3 n ) 的dp了,这个要仔细分析一波才行

代码

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#define pb push_back
using namespace std;
typedef long long ll;
const ll Mod = 1e9+7;
inline ll read()
{
  ll p=0; ll f=1; char ch=getchar();
  while(ch<'0' || ch>'9'){if(ch=='-') f=-1; ch=getchar();}
  while(ch>='0' && ch<='9'){p=p*10+ch-'0'; ch=getchar();}
  return p*f;
}
ll mp[16]; ll cnt[1<<16]; ll f[1<<16],g[1<<16],h[1<<16]; ll bin[16*16];
ll low_bit(ll x){return x&(-x);} ll w[1<<16]; vector<ll>v;
void upd(ll &x,ll y){x=(x+y+Mod)%Mod;}
int main()
{
  ll n = read(); ll m = read();
  bin[0] = 1; for(ll i=1;i<=n*n;i++) bin[i] = bin[i-1] * 2 % Mod;
  for(ll i=1;i<=m;i++){ll x = read(); ll y = read(); x--; y--; mp[x] |= bin[y];}
  for(ll i=0;i<bin[n];i++) cnt[i] = __builtin_popcount(i);
  for(ll i=1;i<bin[n];i++)
  {
    if(low_bit(i) == i){f[i] = 1; g[i] = -1; continue;}
    v.clear(); for(ll j=i;j;j=(j-1)&i) v.pb(j);
    for(ll j=0;j<n;j++) if(i&bin[j]) w[bin[j]] = cnt[mp[j] & i];
    w[0] = 0; for(ll j=v.size()-1;j>=0;j--) w[v[j]] = w[v[j]^low_bit(v[j])] + w[low_bit(v[j])];
    f[i] = bin[w[i]];
    for(ll j=1;j<v.size();j++) if(v[j] & low_bit(i)) upd(g[i],- f[v[j]] * g[i^v[j]] % Mod);
    // for(ll j=i;j;j=(j-1)&i) printf("%lld %lld %lld\n",i,j,g[j]);
    for(ll j=0;j<v.size();j++)
    {
      upd(f[i] , bin[w[i^v[j]]] * g[v[j]] % Mod);
      //printf("%lld %lld\n",i,v[j]);
    }
    upd(g[i],-f[i]);
    for(ll j=0;j<v.size();j++) w[v[j]] = 0;
  }
  return printf("%lld\n",f[bin[n]-1]),0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值