【FJWC2019】子图(容斥)(三元环/四元环计数)

简要题意:

n n n 个点, m m m 条边的无向图,询问含有 k k k 条边的联通子图的个数

n ≤ 1 0 5 , m ≤ 2 ∗ 1 0 5 , k ≤ 4 n≤10^5,m≤2∗10^5,k≤4 n105,m2105,k4


题解:

容斥其实很简单,懒得讲了,如果你不知道这道题的容斥是怎么回事可以参见这里

然后三元环的DAG建图计算方式和四元环的计算方式以及复杂度,我来这里给大家口胡一下

首先是三元环:

给所有边定向,从度数大的连向度数小的,度数相同按照编号从大到小连接。

显然连出来的图是一个DAG。

之后枚举每一个点 u u u ,然后将 u u u 的所有相邻的点打上标记,然后枚举 u u u 的出点 v v v ,再枚举 v v v的相邻的点 w w w,如果 w w w被打了标记,那么 ( u , v , w ) (u,v,w) (u,v,w) 就是一个三元环了。

而且我们能够保证每个三元环只记录了一次,这个算法的复杂度为 O ( m m ) O(m\sqrt m) O(mm )而且度数小的向度数大的连边也是 O ( m m ) O(m\sqrt m) O(mm )

复杂度证明也很简单:
我们发现复杂度瓶颈在于枚举长度为 2 2 2的路径上。

枚举次数显然为 ∑ v o u t v \sum_{v}out_v voutv
o u t v out_v outv分类讨论

  1. o u t v ≤ m out_v≤m outvm,由于 u u u 连接 v v v d u ≥ d v d_u \ge d_v dudv,这样的 u u u 的个数是 O ( n ) O(n) O(n) 的,因此这里的时间复杂度为 O ( n m ) O(n \sqrt m) O(nm )
  2. o u t v > m out_v \gt \sqrt m outv>m ,由于 u u u 连接 v v v d u ≥ d e g v d_u \ge deg_v dudegv,即 d u > m d_u \gt \sqrt m du>m ,这样的 u u u 的个数是 O ( m ) O(\sqrt m) O(m ) 的,因此这里的时间复杂度为 O ( m m ) O(m\sqrt m ) O(mm )

然后是四元环,我们只需要把三元环的打标记过程改一下,改为在线。

具体表述为,我们不用DAG,而是直接建立原图,然后从度数大的点开始枚举,所有后继中有到他距离为2的点就 + = c n t [ v ] +=cnt[v] +=cnt[v],然后将这个点的 c n t + + cnt++ cnt++

最后消除一下影响就行了。删掉这个度数最大的点,继续做。

复杂度的证明是一样的。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define gc get_char
#define cs const

namespace IO{
	inline char get_char(){
		static cs int Rlen=1<<22|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++; 
	}
	
	template<typename T>
	inline T get(){
		re char c;
		while(!isdigit(c=gc()));re T num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
	inline int getint(){return get<int>();}
}
using namespace IO;

using std::cerr;
using std::cout;

cs int mod=1e9+7;
inline int add(int a,int b){return (a+=b)>=mod?a-mod:a;}
inline int dec(int a,int b){return (a-=b)<0?a+mod:a;}
inline void Inc(int &a,int b){(a+=b)>=mod?a-=mod:a;}
inline void Dec(int &a,int b){(a-=b)<0?a+=mod:a;}
inline int mul(int a,int b){return (ll)a*b>=mod?(ll)a*b%mod:(ll)(a*b);}
template<class ...Args>
inline int mul(int a,cs Args &...args){return mul(a,mul(args...));}
template<class ...Args>
inline int add(int a,cs Args &...args){return add(a,add(args...));}

cs int inv2=(mod+1)>>1,inv3=(mod+1)/3,inv4=mul(inv2,inv2),inv6=mul(inv3,inv2),inv24=mul(inv4,inv6); 

inline int c2(int n){return mul(n,n-1,inv2);}
inline int c3(int n){return mul(n,n-1,n-2,inv6);}
inline int c4(int n){return mul(n,n-1,n-2,n-3,inv24);}

cs int N=1e5+5;

int n,m,k;

struct edge{int u,v;}E[N<<1];

std::vector<int> G[N];
inline void addedge(int u,int v){
	G[u].push_back(v);
}
int d[N],d2[N],d3[N];
int r3[N],r4[N],cnt3,cnt4;

int vis[N];

inline bool cmp(int x,int y){return d[x]>d[y]||(d[x]==d[y]&&x>y);}

inline void calc_3(){
	for(int re u=1;u<=n;++u){
		for(int re v:G[u])++vis[v];
		for(int re v:G[u])for(int re w:G[v])vis[w]&&(++r3[u],++r3[v],++r3[w]);
		for(int re v:G[u])vis[v]=0;
	}
	for(int re u=1;u<=n;++u)Inc(cnt3,r3[u]);
	cnt3=mul(cnt3,inv3);
}

inline void calc_4(){
	for(int re u=1;u<=n;++u){
		for(int re v:G[u])if(cmp(u,v))for(int re w:G[v])cmp(u,w)&&(r4[u]+=vis[w],r4[v]+=vis[w],r4[w]+=vis[w],++vis[w]);
		for(int re v:G[u])if(cmp(u,v))for(int re w:G[v])cmp(u,w)&&(r4[v]+=--vis[w]);
	}
	for(int re u=1;u<=n;++u)Inc(cnt4,r4[u]);
	cnt4=mul(cnt4,inv4);
}

inline void cal2(){
	int ans=0;
	for(int re i=1;i<=m;++i)++d[getint()],++d[getint()];
	for(int re i=1;i<=n;++i)Inc(ans,c2(d[i]));
	cout<<ans<<"\n";
}

inline void cal3(){
	int ans=0;
	for(int re i=1;i<=m;++i)++d[E[i].u=getint()],++d[E[i].v=getint()];
    for(int re i=1;i<=m;++i)cmp(E[i].u,E[i].v)?addedge(E[i].u,E[i].v):addedge(E[i].v,E[i].u);
    calc_3();
    for(int re i=1;i<=n;++i)Inc(ans,c3(d[i]));
    for(int re i=1;i<=m;++i)Inc(ans,mul(d[E[i].u]-1,d[E[i].v]-1));
    Dec(ans,add(cnt3,cnt3));
    cout<<ans<<"\n";
}

inline int calc(){
	int res=0;
	for(int re u=1;u<=n;++u)Inc(res,mul(r3[u],d[u]-2));
	return res;
}

inline void cal4(){
	int ans=0;
	for(int re i=1;i<=m;++i)++d[E[i].u=getint()],++d[E[i].v=getint()];
	for(int re i=1;i<=m;++i)cmp(E[i].u,E[i].v)?addedge(E[i].u,E[i].v):addedge(E[i].v,E[i].u);
	calc_3();
	for(int re i=1;i<=m;++i)cmp(E[i].u,E[i].v)?addedge(E[i].v,E[i].u):addedge(E[i].u,E[i].v);
	calc_4();
	for(int re i=1;i<=n;++i)Inc(ans,c4(d[i]));
	for(int re i=1;i<=m;++i)Inc(ans,add(mul(c2(d[E[i].u]-1),d[E[i].v]-1),mul(c2(d[E[i].v]-1),d[E[i].u]-1)));
	Dec(ans,mul(3,calc()));
	for(int re u=1;u<=n;++u){
		int t=0;
		for(int re v:G[u])
		Inc(ans,mul(t,d[v]-1)),
		Inc(t,d[v]-1);
	}
	Dec(ans,mul(3,cnt3));
	Dec(ans,mul(3,cnt4));
	cout<<ans<<"\n";
}

signed main(){
	n=getint(),m=getint(),k=getint();
	switch(k){
		case 1:cout<<m<<"\n";break;
		case 2:cal2();break;
		case 3:cal3();break;
		case 4:cal4();break;
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值