bzoj 4671: 异或图 容斥+斯特林反演+线性基

7 篇文章 0 订阅
3 篇文章 0 订阅

题意

定义两个结点数相同的图 G1 与图 G2 的异或为一个新的图 G, 其中如果 (u, v) 在 G1 与
G2 中的出现次数之和为 1, 那么边 (u, v) 在 G 中, 否则这条边不在 G 中.
现在给定 s 个结点数相同的图 G1…s, 设 S = {G1, G2, … , Gs}, 请问 S 有多少个子集的异
或为一个连通图?

分析

我们有一个想法,就是枚举子图的子集,然后再来搞,这样显然不行,因为m有60这么大

另外一个方向,我们从n开始入手,用贝尔数的时间(其实就是第二类斯特林数的一行的和)来进行把这些点划分,然后同一个集合里面的点可以有边也可以没有,不同集合一定没边

至于算方案数,我们可以把每一条不在集合的边变成一个二进制位,有边则为1,没有则为0,插入线性基,看看自由元个数,假设为cnt,那么答案就贡献

2cnt 2 c n t

考虑这个方案数其实代表什么,代表的就是一些子图子集的划分,变成这样的一个至少为m联通块的方案数

考虑一种子图子集的划分,会被算到的次数是

i=1mS(m,i) ∑ i = 1 m S ( m , i )

之后另外一个现实,有一个容斥系数为
(1)m1(m1)! ( − 1 ) m − 1 ( m − 1 ) !

就可以算出联通块个数恰好为n=1的情况
下面简单证明一下:
m=1n(1)m1(m1)!S(n,m)=m=1n1(1)m1(m1)!(S(n1,m1)+mS(n1,m))+(1)n1(n1)!=m=1n1(1)m1(m1)!S(n1,m1)+m=1n1(1)m1m!S(n1,m)+(1)n1(n1)!=m=1n1(1)m1(m1)!S(n1,m1)+m=1n2(1)m1m!S(n1,m)=0[n>1](1) (1) ∑ m = 1 n ( − 1 ) m − 1 ( m − 1 ) ! S ( n , m ) = ∑ m = 1 n − 1 ( − 1 ) m − 1 ( m − 1 ) ! ( S ( n − 1 , m − 1 ) + m S ( n − 1 , m ) ) + ( − 1 ) n − 1 ( n − 1 ) ! = ∑ m = 1 n − 1 ( − 1 ) m − 1 ( m − 1 ) ! S ( n − 1 , m − 1 ) + ∑ m = 1 n − 1 ( − 1 ) m − 1 m ! S ( n − 1 , m ) + ( − 1 ) n − 1 ( n − 1 ) ! = ∑ m = 1 n − 1 ( − 1 ) m − 1 ( m − 1 ) ! S ( n − 1 , m − 1 ) + ∑ m = 1 n − 2 ( − 1 ) m − 1 m ! S ( n − 1 , m ) = 0 [ n > 1 ]

显然只有当n=1时上式=1
然后就没了,这个东西叫做斯特林反演?
还要 卡卡常

代码

#include <bits/stdc++.h>
#define ll long long
#define bin(i) (1ll<<(i))
#define c(i,j) (fac[(i)] * inv[(j)] % mod * inv[(i) - (j)] % mod)
using namespace std;

const int N = 65;

inline int read()
{
  char ch=getchar(); int p=0; int f=1;
  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;
}

int s; char ss[12*12]; int n;

bool v[N][12][12];

int bel[12]; int fac[12]; int cnt; ll b[N],ans = 0;

void dfs(ll x,ll y)
{
  if(x>n)
  {
    cnt = 0;
    for(int k=1;k<=s;k++)
    {
      int l=0; ll t=0;
      for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) if(bel[i] != bel[j]) t|=bin(l) * v[k][i][j],l++;
      // printf("%lld\n",t);
      for(int i=1;i<=cnt;i++) if((t^b[i]) < t) t^=b[i];
      if(t) b[++cnt] = t;
    }

    ans = ans + bin(s-cnt) * ((y&1) ? 1 : (-1)) * fac[y-1];
    // printf("%lld\n",bin(s-cnt) * ((y&1) ? 1 : (-1)) * fac[y-1]);
    // printf("%lld %lld %lld\n",x,y,bin(s-cnt) * ((y&1) ? 1 : (-1)) * fac[y-1]);
    return ;
  }
  for(int i=1;i<=y+1;i++) bel[x] = i,dfs(x+1,y+(i>y));
}
int main()
{
  s = read();
  for(int i=1;i<=s;i++)
  {
    scanf("%s",ss+1); int len = strlen(ss + 1);
    for(int j=1;j<=10;j++) if(j*(j-1)/2 == len){n=j; break;}
    int l=1; for(int j=1;j<=n;j++) for(int k=j+1;k<=n;k++,l++) if(ss[l]=='1') v[i][j][k] = v[i][k][j] = 1;
  }
  fac[0] = 1; for(int i=1;i<=n;i++) fac[i] = fac[i-1] * i ;
  dfs(1,0);
  return printf("%lld\n",ans),0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值