Agc011_C Squared Graph

传送门

题目大意

给定$n$个点$m$条边的简单图(无重边无自环),将有序点对$\{a,b\}$作为新的点,新产生的$n^2$个点中对于两个点,$\{a,b\},\{x,y\}$,当且仅当原图中存在边$(a,x)$和$(b,y)$,则在新图中产生边$(\{a,b\},\{x,y\})$。求新图中连通块数。

 

题解

想一想新图的两个点联通有什么条件,就能渐渐推理出这个很显然的性质。

若新图中$\{a,b\}$与$\{x,y\}$连通,则在原图中:

$a,x$连通,$b,y$连通。

$a,x$之间和$b,x$存在一条长度相同的路径(可以不是简单路径)

若$a,x$连通、$b,y$连通,当且仅当$a,x$间只存在长为奇数的路径,$b,y$之间只存在长为偶数的路径(或相反),在新图中$\{a,b\}$,$\{b,y\}$不连通。

若两个原图连通块$A,B$,对于有的点$\space a,\space x\in A$,$b,y\in B\space$,当且仅当$A,B$均不存在奇环时,即$A,B$均为二分图时才可能出现$\{a.b\}$与$\{x,y\}$不连通,且会在新的图中恰好分为$2$个连通块。

我们需要特判单点(度数为$0$)的情况:对所有至少包含一个原图单点的新图的点,它一定自成一个连通块。

计有$K$个度数为$0$的单点,有$P$个二分图连通块,$Q$个非二分图连通块,由于新图中每一个连通块一定有原图中的一个或两个连通块产生,我们可以直接计算答案。$$Ans=\space K(2n-1)-K(K-1)\space + \space P^2\space +\space Q^2\space +\space 2\times P\cdot Q$$

注意新图中的点对是有序,即$(a,b)$与$(b,a)$不同,第一部分是包含一个读数为$0$的单点的点对数,接下来的两个是枚举前面的点和后面的点在哪一个原图的性质相同的连通块里,即(都有奇环或都没有),其中两个二分图会产生两组,所以要乘$2$。最后那个部分是枚举前后的点在不同的连通块中,因为前后有别所以要乘$2$。

 

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define LL long long
#define M 200020
using namespace std;
LL read(){
	LL nm=0,fh=1; char cw=getchar();
	for(;!isdigit(cw);cw=getchar()) if(cw=='-') fh=-fh;
	for(;isdigit(cw);cw=getchar()) nm=nm*10+(cw-'0');
	return nm*fh;
}
LL ans,sum0,sum1,sig;
LL n,m,fs[M],nt[M<<1],to[M<<1],col[M],f[M],sz[M],tmp;
LL fd(LL x){return x==f[x]?x:f[x]=fd(f[x]);}
void link(LL x,LL y){nt[tmp]=fs[x],fs[x]=tmp,to[tmp++]=y;}
void merge(LL x,LL y){x=fd(x),y=fd(y);if(x!=y) f[x]=y,sz[y]+=sz[x];}
bool check(LL x,LL now){
	if(col[x]) return col[x]==now; col[x]=now;
	for(LL i=fs[x];i!=-1;i=nt[i])
		if(!check(to[i],3-now)) return false;
	return true;
}
int main(){
	n=read(),m=read(),memset(fs,-1,sizeof(fs));
	for(LL i=1;i<=n;i++) f[i]=i,sz[i]=1;
	for(LL i=1;i<=m;i++){
		LL x=read(),y=read();
		link(x,y),link(y,x),merge(x,y);
	}
	for(LL i=1;i<=n;i++) if(fd(i)==i&&sz[i]==1) sig++;
	for(LL i=1;i<=n;i++){
		if(fd(i)!=i||sz[i]==1) continue;
		if(check(i,1)) sum0++;
		else sum1++;
	}
	ans+=sig*((n<<1)-1)-sig*(sig-1);
	ans+=((sum0*sum0)<<1);
	ans+=sum1*(sum0<<1)+sum1*sum1;
	printf("%lld\n",ans);
	return 0;
}

 

  

 

posted @ 2018-09-29 15:34  OYJason  阅读(290)  评论(0编辑  收藏  举报