Description
给你一个n行n列的网格,第i行第j列的格子用(i,j)表示。
一开始的时候有m个格子被涂成黑色,其他的格子都是白色,具体一点,涂成黑色的格子为(a1,b1),(a2,b2),(a3,b3),…,(am,bm)。
你的目标是按照以下规则将尽可能多的白色格子涂成黑色:如果存在三个格子(x,y),(y,z),(z,x)满足(x,y)和(y,z)都是黑格子并且(z,x)是白格子(其中x,y,z都是[1,n]之间的整数),那么你可以将(z,x)涂成黑色。
输出你不能继续操作时,黑格子的最大数量。
Solution
我们可以将其转化为有向图,若存在(x,y)和(y,z)这两条边,则我们可以添加第三条边(z,x),求边的最大数量。
首先,各弱联通块之间互不影响,因此我们可以将各弱联通块分开考虑。
我们可以得到:
(1)一条长度为2的链可形成一个三元环。(x,y)(y,z)→(x,y)(y,z)(z,x)
(2)一个二元环会形成自环。(x,y)(y,x)→(x,y)(y,x)(x,x)(y,y)
(3)所有连向自环点的点都会形成自环。(x,y)(y,y)→(x,y)(y,y)(y,x)(x,x)
(4)由(2)和(3)得若一个弱联通块存在二元环或自环,则该弱联通块可拓展为完全图。
我们考虑各弱联通块是否存在二元环或自环,用三种颜色进行染色(如三元环中三个点为不同的颜色),进行分类讨论:
(1)只出现了一种或两种颜色:说明该弱联通块无法添加新边;
(2)同一个点被染上两种或三种颜色:出现二元环或自环,该弱联通块可拓展为完全图;
(3)default:染色成功,染色为0的点连向染色为1的点,染色为1的点连向染色为2的点,染色为2的点连向染色为0的点。
Code
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 #define next _next 6 typedef long long ll; 7 struct edge{ 8 int to,next,data; 9 }e[200010]; 10 int n,m,head[100010]; 11 int tot,size,maxn,a[3],color[100010]={0}; 12 ll ans=0; 13 bool flag,vis[100010]={false},visp[100010]={false}; 14 void dfs(int u){ 15 vis[u]=true; 16 size++; 17 a[color[u]]++; 18 maxn=max(maxn,color[u]); 19 for(int i=head[u];~i;i=e[i].next) 20 if(!visp[(i+1)/2]){ 21 visp[(i+1)/2]=true; 22 tot++; 23 int v=e[i].to; 24 if(!vis[v]){ 25 color[v]=(color[u]+e[i].data+3)%3; 26 dfs(v); 27 } 28 else 29 flag|=(color[v]!=(color[u]+e[i].data+3)%3); 30 } 31 return; 32 } 33 int main(){ 34 memset(head,-1,sizeof(head)); 35 scanf("%d%d",&n,&m); 36 for(int i=1,cnt=0;i<=m;i++){ 37 int u,v; 38 scanf("%d%d",&u,&v); 39 e[++cnt]=(edge){v,head[u],1}; 40 head[u]=cnt; 41 e[++cnt]=(edge){u,head[v],-1}; 42 head[v]=cnt; 43 } 44 for(int i=1;i<=n;i++) 45 if(!vis[i]){ 46 tot=size=maxn=a[0]=a[1]=a[2]=0; 47 flag=false; 48 dfs(i); 49 if(flag) 50 ans+=(ll)size*size; 51 else if(!a[0]||!a[1]||!a[2]) 52 ans+=tot; 53 else 54 ans+=(ll)a[0]*a[1]+(ll)a[1]*a[2]+(ll)a[2]*a[0]; 55 } 56 printf("%lld\n",ans); 57 return 0; 58 }