AT2167 [AGC006F] Blackout

题意简述

有一张 n×nn\times n 的网格,一开始共有 mm 个格子被涂成黑色,其它所有格子都是白色的。

如果存在三个正整数 x,y,zx, y, z1x,y,zn1\le x, y, z\le n),满足 (x,y)\left(x, y\right)(y,z)\left(y, z\right) 都是黑色的,那么就可以将 (z,x)\left(z, x\right) 涂成黑色。

求最多能使棋盘上有多少个黑色的格子。

思路

以下部分内容来自于教练的讲义,融入了笔者的思考

认真思考,我们可以发现以下性质
如果 (x,y)\left(x, y\right) (y,z)\left(y, z\right)(z,x)\left(z, x\right) 中任两个是黑色的,则第三个是黑色的。

更进一步的是,如果对任意 xX,zZx\in X, z\in Z,都存在 y0Yy_0 \in Y 满足 (x,y0)\left(x, y_0\right)(y0,z)\left(y_0, z\right) 同时为黑,那么 (z,x)\left(z,x\right) 也是黑的。

我们不妨把一个黑色的格子当做两个元素之间产生关联。这种关联是单向的,即 (x,y)\left(x, y\right) 是黑的不能得出 (y,x)\left(y, x\right) 也是黑的。

考虑在膜 33 的意义下建图

我们假设有三个互不相交非空集合正解 X,Y,ZX, Y, Z 他们分别用 0,1,20,1,2,每个元素必然存在于其中一个点集。

我们可以假设 XXYYYYZZ 的距离为11.

那么在膜 33 意义下,各集合之间的距离就可以算出,如 ZZXX 的距离应该为 11,因为在膜 33 意义下 (1+2)=0\left(1 + 2\right)=0

显然对于不同连通块,我们可以分开处理。我们可以分成一下三种情况处理。同样显然的是,每两点存在联系,就表明存在一个黑块。

  1. 如果一个连通块的点集可以被划分为两个不相交的集合,且只有其中一个集合的点向另一个集合连边,每个集合内部没有连边,那么这个连通块不能产生新的边。此时仅需统计边数即可,值得注意的是,因为我们建的是有向图,所以统计出来的边数除以 22 即可。

  2. 如果一个连通块的点集可以被划分为三个不相交的集合 X,Y,ZX,Y,Z,且 XX 中的每个点都与 YY 中的至少一个点连边,其他集合间的关系相同。那么这个连通块的最终状态为 XX 中所有点向 YY 中所有点连边,其他两个集合关系相同。最终边数为 X×Y+Y×Z+X×ZX \times Y + Y \times Z + X \times Z

  3. 如果一个连通块不符合上面说的任意一种情况,如集合内部有连边,则最终状态为集合内部所有点对之间都有连边(包括自环)。最终边数为点数平方

具体怎么判断呢,我们可以进行染色,对于每个节点,我们用 col[]col\left[\right] 记录下它所在的集合(同上图三个集合),对于任意两个联通点,如果 col[v] != (col[u] + e[j].w) % 3 则说明他们不分属两个集合,即集合内有边相连。对于上述 2\texttt{2} 仅需判断是否三个集合内都有点即可。

综合时间复杂度 O(n+m)\mathcal{O}\left(n+m\right),可以通过本题,当然了你不吸氧手写队列也并不复杂。
WFJqf0.md.png

还算过得去/cy

code

#include<bits/stdc++.h>
#define Maxn 100007
#define ll long long
using namespace std ;
struct Edge 
{
	int to , w , next ;
}e[Maxn << 1] ;

int head[Maxn] , col[Maxn] , cnt[3] , tot , n , m , check , edgecnt ;
ll ans ; 
bool vis[Maxn] ;
queue <int> q ;
void add(int x , int y , int w)
{
	e[++ tot].to = y , e[tot].next = head[x] , head[x] = tot , e[tot].w = w ;
}
void clear(queue<int>& q)
{
    queue<int> empty ;
    swap(empty , q) ;
}
int main()
{
	int u , v ;
	cin >> n >> m ;
	for(int i = 1 ; i <= m ; i ++)
	{
		cin >> u >> v ;
		add(u , v , 1) ;
		add(v , u , 2) ;
	}
	for(int i = 1 ; i <= n ; i ++)
		if(! vis[i])
		{
			check = edgecnt = cnt[0] = cnt[1] = cnt[2] = 0 ;
			clear(q) ; q.push(i) ;
			vis[i] = 1 ;
			while(! q.empty())
			{
				u = q.front() ;
				q.pop() ;
				++ cnt[col[u]] ;
				for(int j = head[u] ; j ; j = e[j].next)
				{
					++ edgecnt ;
					v = e[j].to ;
					if(vis[v])
						if(col[v] != (col[u] + e[j].w) % 3) check = 1 ;
					if(! vis[v])
					{
						vis[v] = 1 ; 
						col[v] = (col[u] + e[j].w) % 3 ;
						q.push(v) ;
					}
				}
			}
			if(check) ans += 1ll * (cnt[0] + cnt[1] + cnt[2]) * (cnt[0] + cnt[1] + cnt[2]) ;
			else if(cnt[0] && cnt[1] && cnt[2]) ans += 1ll * cnt[0] * cnt[1] + 1ll * cnt[1] * cnt[2] + 1ll * cnt[2] * cnt[0] ;
			else ans += edgecnt >> 1 ;
		}
		cout << ans ;
		return 0 ;
}