[agc006f]blackout
题意:
给你一个$n\times n$的网格,开始有$m$个被涂成黑色的格子,如果存在三个格子$(x,y)$,$(y,z)$,$(z,x)$满足$(x,y)$,$(y,z)$均为黑格子且$(z,x)$为白格子,那么就将$(z,x)$涂黑,问最后会有多少个被涂黑的格子。
题解:
神仙题。。。感觉正解比C题还神仙。。。
用矩阵直接构造非常麻烦,矩阵甚至构造不出来,那么可以考虑对每个黑格子$(x,y)$,从$x$向$y$连边,那么问题就转化到了有向图上。(真是让人只能跪地膜的构造,场上完全没这样想过。。。)这样整个矩阵就变成了一个邻接矩阵,题意就变成了如果图中有两条边$<x,y>$,$<y,z>$,那么就连一条边$<z,x>$;
显然每个弱联通块之间没有关系,所以每个联通块单独考虑。大(sui)胆(bian)证(shou)明(wan)之后可以发现几个性质:
1.一条长度为2的链会变成一个三元环;
2.一个二元环会形成一个自环;
3.只要形成自环,那么整个联通块就会变成一个完全图;
所以考虑判断这个联通块有没有自环or二元环,这个有固定技巧,即将原图三染色(012染色),然后分类讨论:
1.如果染色失败(即颜色冲突),那么说明联通块中出现了自环或二元环,变成了完全图,那么直接把边数平方即可;
2.如果三种颜色没有全部出现,说明图中没有长度大于一的链,此时不会出现新的边,即不会出现新格子;
3.如果染色成功且三种颜色都出现了,那么答案就是0的点向1的连边,1的点向2的连边,2的点向3的连边,直接乘起来即可;
代码:
1 #include<algorithm>
2 #include<iostream>
3 #include<cstring>
4 #include<cstdio>
5 #include<cmath>
6 using namespace std;
7 typedef long long ll;
8 struct edge{
9 int v,w,next;
10 }a[500001];
11 int n,m,u,v,c[3],cnt,_cnt,cc[500001],tot=0,head[500001];
12 bool used[500001],ok;
13 ll ans=0;
14 void add(int u,int v,int w){
15 a[++tot].v=v;
16 a[tot].w=w;
17 a[tot].next=head[u];
18 head[u]=tot;
19 }
20 void dfs(int u){
21 c[cc[u]]++;
22 used[u]=true;
23 cnt++;
24 for(int tmp=head[u];tmp!=-1;tmp=a[tmp].next){
25 int v=a[tmp].v;
26 if(a[tmp].w==1)_cnt++;
27 if(!used[v]){
28 cc[v]=(cc[u]+a[tmp].w+3)%3;
29 dfs(v);
30 }else if(cc[v]!=(cc[u]+a[tmp].w+3)%3)ok=false;
31 }
32 }
33 int main(){
34 memset(head,-1,sizeof(head));
35 memset(cc,-1,sizeof(cc));
36 memset(used,0,sizeof(used));
37 scanf("%d%d",&n,&m);
38 for(int i=1;i<=m;i++){
39 scanf("%d%d",&u,&v);
40 add(u,v,1);
41 add(v,u,-1);
42 }
43 for(int i=1;i<=n;i++){
44 if(!used[i]){
45 c[0]=c[1]=c[2]=cnt=_cnt=0;
46 ok=1;
47 cc[i]=0;
48 dfs(i);
49 if(!ok)ans+=(ll)cnt*cnt;
50 else if(c[0]&&c[1]&&c[2])ans+=(ll)c[0]*c[1]+(ll)c[0]*c[2]+(ll)c[1]*c[2];
51 else ans+=_cnt;
52 }
53 }
54 printf("%lld",ans);
55 return 0;
56 }