BZOJ 3812 主旋律

本文探讨了计算非强连通子图数量的高效算法,通过定义fs和gs来分别表示由特定点集构成的强连通图数量和非强连通图方案数,进而推导出计算公式。利用动态规划思想,结合位运算优化计算过程,最终给出完整代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接

https://www.lydsy.com/JudgeOnline/problem.php?id=3812

题解

考虑求非强连通子图的数量,假设为 g g g,那么答案就是 2 m − g 2^m-g 2mg。现在考虑求 g g g

假设 f s f_s fs表示用 s s s这些点能构成的强连通图的个数, g s g_s gs表示用 s s s这些点能构成的非强连通图的方案数,其中构成 i i i个强连通分量则对 g s g_s gs的贡献为 ( − 1 ) i (-1)^i (1)i。容易发现
g s = f s − ∑ t ⊂ s , u ∈ t g t f s − t g_s=f_s-\sum_{t\subset s,u\in t} g_tf_{s-t} gs=fsts,utgtfst
那么
f s = 2 e s − ∑ t ⊆ s , t ̸ = ∅ 2 e s − t + e s − t , t g s f_s=2^{e_s}-\sum_{t\subseteq s,t\not= \varnothing}2^{e_{s-t}+e_{s-t,t}}g_s fs=2ests,t̸=2est+est,tgs
容易发现, f s f_s fs此时需要的是不包含 f s f_s fs g s g_s gs,因此 g s g_s gs在求出 f s f_s fs之前是不能 + f s +f_s +fs的。

代码

#include <cstdio>
 
int read()
{
  int x=0,f=1;
  char ch=getchar();
  while((ch<'0')||(ch>'9'))
    {
      if(ch=='-')
        {
          f=-f;
        }
      ch=getchar();
    }
  while((ch>='0')&&(ch<='9'))
    {
      x=x*10+ch-'0';
      ch=getchar();
    }
  return x*f;
}
 
const int maxn=15;
const int maxm=1<<maxn;
const int mod=1000000007;
 
int n,m,f[maxm+10],g[maxm+10],pow[maxn*maxn+10],ecnt[maxm+10][maxn+2],in[maxm+10];
 
inline int lowbit(int x)
{
  return x&(-x);
}
 
int main()
{
  n=read();
  m=read();
  int full=(1<<n)-1;
  for(int i=1; i<=m; ++i)
    {
      int a=read(),b=read();
      for(int j=1; j<=full; ++j)
        {
          if((1<<(a-1))&j)
            {
              ++ecnt[j][b];
            }
        }
    }
  pow[0]=1;
  for(int i=1; i<=m; ++i)
    {
      pow[i]=pow[i-1]<<1;
      if(pow[i]>=mod)
        {
          pow[i]-=mod;
        }
    }
  f[0]=g[0]=1;
  for(int s=1; s<=full; ++s)
    {
      int sk=s^lowbit(s);
      for(int t=sk; t; t=sk&(t-1))
        {
          g[s]-=1ll*f[s^t]*g[t]%mod;
          if(g[s]<0)
            {
              g[s]+=mod;
            }
        }
      for(int i=1; i<=n; ++i)
        {
          if((1<<(i-1))&s)
            {
              in[s]+=ecnt[s][i];
            }
        }
      f[s]+=pow[in[s]];
      for(int t=s; t; t=s&(t-1))
        {
          int e=0;
          for(int i=1; i<=n; ++i)
            {
              if((1<<(i-1))&t)
                {
                  e+=ecnt[s^t][i];
                }
            }
          f[s]-=1ll*pow[e+in[s^t]]*g[t]%mod;
          if(f[s]<0)
            {
              f[s]+=mod;
            }
        }
      g[s]+=f[s];
      if(g[s]>=mod)
        {
          g[s]-=mod;
        }
    }
  printf("%d\n",f[full]);
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值