洛谷 AT2167 Blackout 题解
题目描述
我们有一个\(N\)行\(N\)列的矩阵。第\(i\)行第\(j\)列的格子表示为\((i,j)\)。
开始时,有\(M\)个格子是黑色,其他格子都是白色。特别地,开始时格子\((a1,b1),(a2,b2),...,(am,bm)\)是黑色。
スヌケ君会按照以下的规则尽可能多的将白色格子涂成黑色:
- 对于整数\(1≤x,y,z≤N\),如果\((x,y)\)和\((y,z)\)都是黑色,那么就把\((z,x)\)涂黑。
请计算出当再也没有白色格子能被涂黑时,黑色格子的个数。
说明
- \(1≤N≤10^5\)
- \(1≤M≤10^5\)
- \(1≤ai,bi≤N\)
- 各黑格坐标互不相同
思路
对于这样一个迷之题面大家第一反应大概是神题不可做,然而显然是可以做的,我们对每一个黑色方块\((x,y)\)建立一条有向边,边权记为\(1\),为了方便后续对这张图的遍历、染色工作,我们再建立一条反向的\((y,x)\),不过为了与描述黑色方块的边区分开来,将边权标记为\(-1\).
考虑图中的每一个联通块,我们对其进行三染色操作,基本染色原则为\(r\) -> \(g\) -> \(b\) -> \(r\).
染色完毕后,我们不难发现以下规律:
-
对于每个无法成功染色的联通块(颜色相互冲突),根据题意,我们都可以构造一张可以含有自环的完全图(注意:根据题意,形如\((x,x)\)的点是可行的).显然,这样的结构对答案的贡献为\(n^2\).
-
对于每个可以成功染色的联通块,如果三种颜色均包含在内,那么根据题意,该联通块中每一对\(r\) -> \(g\), \(g\) -> \(b\),\(b\) -> \(r\)均可以连出一条有向边(暨染成黑色).若约定用\(n(r)\)表示该种颜色的节点在联通块中的数量,这样的结构对答案的贡献可以表示为\(n(r)*n(g) + n(g) * n(b) + n(b) * n(r)\).
-
对于每个可以成功染色的联通块,如果仅有两种或更少的颜色包含在内,那么显然我们无法染出更多的黑块.则这样的结构对答案没有额外贡献.
根据如上三条规律,我们不难写出如下程序。不过具体地,一个小小的细节是由于答案可能过大,我们需要用\(long long\)型变量对答案进行储存。并且,我们要时刻注意\(int\)乘法中可能存在的溢出问题,解决方案很简单,在每一个\(int\)乘法运算前加上\((long long)\)即可。
AC代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cctype>
using namespace std;
inline int read(){
int re = 0,ch = getchar();
while(!isdigit(ch))ch = getchar();
while(isdigit(ch))re = (re<<1)+(re<<3)+ch-'0',ch = getchar();
return re;
}
const int maxn = 200010;
struct edge{
int v,w,nxt;
}e[maxn];
int h[maxn],cnt;
inline void add(int u,int v,int w){
e[++cnt].v = v;
e[cnt].w = w;
e[cnt].nxt = h[u];
h[u] = cnt;
}
inline int color(int c){
if(c >= 3)c -= 3;
if(c < 0)c += 3;
return c;
}
int col[maxn],cnum[3],nsz,esz;
bool flag = 1,vis[maxn];
void coloring(int u){
cnum[col[u]]++;nsz++;vis[u] = 1;
for(int i = h[u];i;i = e[i].nxt){
int v = e[i].v;
if(e[i].w == 1)esz++;
if(!vis[v]) col[v] = color(col[u] + e[i].w),coloring(v);
else if(col[v] != color(col[u] + e[i].w)) flag = 0;
}
}
int n,m;
long long ans;
int main(){
n = read(),m = read();
int u,v;
for(int i = 1;i <= m;i++){
u = read(),v = read();
add(u,v,1);
add(v,u,-1);
}
for(int i = 1;i <= n;i++){
if(vis[i])continue;
memset(cnum,0,sizeof(cnum));
nsz = 0,esz = 0,flag = 1;
coloring(i);
if(!flag) ans += (long long)nsz * nsz;
else if(cnum[0] && cnum[1] && cnum[2])
ans += (long long)cnum[0] * cnum[1]
+ (long long)cnum[1] * cnum[2] + (long long)cnum[2] * cnum[0];
else ans += esz;
}
printf("%lld",ans);
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步