zoukankan      html  css  js  c++  java
  • 【agc006f】Blackout(神仙题)

    【agc006f】Blackout(神仙题)

    翻译

    给定一个(n*n)的网格图,有些格子是黑色的。如果((x,y),(y,z))都是黑色的,那么((y,x))也会被染黑,求最终黑格子数量。

    题解

    网格图我们显然是存不下的,把它转化成图来考虑。于是题目变成了:给定一个(n)个点(m)条边的图,如果(x ightarrow y)(y ightarrow z)的边都存在,那么连边(z ightarrow x),回答边的数量。

    然后开始手动翻译题解。

    首先,我们可以计算每一个弱联通块(把边看成无向边的联通块),那么答案显然就是所有弱联通块的答案的总和。我们先假定图是一个弱联通图。

    考虑这样一种情况,我们把点依次标号,然后在(i)(i+1)之间连边,那么如果(s)(t)之间存在边(s ightarrow t),那么当且仅当(tequiv s+1(mod 3))。具有一定启发意义,我们考虑在模(3)的意义下搞点事情。我们用(A,B,C)给所有点做标记,并且强制要求对于任意一条边,只可能是(A ightarrow B)(B ightarrow C)(C ightarrow A)。这样标号的方式可能不存在,但是不难证明一旦存在合法的标号方案,那么标号的方法唯一(不考虑循环(ABC)的顺序)。你可以把整个图给(dfs)一遍,这样子可以得到唯一的染色方案,或者证明它不存在。

    通过标号的结果,我们可以得到三种情况,给出每种情况下的结论,等下再给出证明。

    • 当标号存在,但是并没有用到所有的三种颜色,那么你无法在这个联通块中进行任何操作。
    • 当标号存在,并且所有的三种颜色都被用到,那么你可以把所有(AB)之间连边,(BC)之间连边,(CA)之间连边,并且只能连这些边。
    • 当标号方案不存在,你可以给任意一对点之间连边,包括自环。

    利用结论,可以很容易的计算出答案,时间复杂度(O(m))。代码如下,证明内容(当然是翻译的啊)在代码后面。

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<vector>
    using namespace std;
    #define ll long long
    #define MAX 100100
    inline int read()
    {
    	int x=0;bool t=false;char ch=getchar();
    	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    	if(ch=='-')t=true,ch=getchar();
    	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    	return t?-x:x;
    }
    struct Line{int v,next,w;}e[MAX<<1];
    int h[MAX],cnt=1,dg[MAX];
    inline void Add(int u,int v,int w){e[cnt]=(Line){v,h[u],w};h[u]=cnt++;}
    int n,m;ll ans;
    int vis[MAX],f[3],edge,size;bool label;
    void dfs(int u,int d)
    {
    	vis[u]=d;f[d]+=1;++size;
    	for(int i=h[u];i;i=e[i].next)
    	{
    		int v=e[i].v;if(e[i].w==1)++edge;
    		if(vis[v]==-1)dfs(v,(d+e[i].w)%3);
    		else if(vis[v]!=(vis[u]+e[i].w)%3)label=false;
    	}
    }
    int main()
    {
    	n=read();m=read();
    	for(int i=1;i<=m;++i)
    	{
    		int x=read(),y=read();
    		Add(x,y,1);Add(y,x,2);
    	}
    	memset(vis,-1,sizeof(vis));
    	for(int i=1;i<=n;++i)
    		if(vis[i]==-1)
    		{
    			label=true;f[0]=f[1]=f[2]=0;size=edge=0;dfs(i,0);
    			if(label)ans+=(!min(f[0],min(f[1],f[2])))?(edge):(1ll*f[0]*f[1]+1ll*f[1]*f[2]+1ll*f[2]*f[0]);
    			else ans+=1ll*size*size;
    		}
    	cout<<ans<<endl;
    	return 0;
    }
    
    • 当标号存在,但是并没有用到所有的三种颜色,那么你无法在这个联通块中进行任何操作。

      如果存在边((x,y))((y,z)),那么必定意为这所有的三种颜色都会被用到。既然如此,那么意味着这里不存在上述的边,所以你不能连出任何一条新边。

    • 当标号存在,并且所有的三种颜色都被用到,那么你可以把所有(AB)之间连边,(BC)之间连边,(CA)之间连边,并且只能连这些边。

      必定存在若干形如((x,y),(y,z))这样的边,我们不妨令(x)(A)(y)(B)(z)(C)。我们可以看出所有新连的边加上原边会构成一个个三角形。举个例子,令(v)存在一条边((v,x)),那么必定存在边((y,v)),那么我们不难证明任意一个(v)一定和(x,y,z)三个点中的两个有直接的边相连。所以任意的(A)都会连出一条(A ightarrow B),其他的边同理。

    • 当标号方案不存在,你可以给任意一对点之间连边,包括自环。

      我们证明至少会存在一个自环。既然标号方案不存在,那么必定存在一个环导致了矛盾,注意,这个环不一定是有向环。那么这个环至少存在两条边((x,y)),((y,z)),那么我们可以连上((z,x)),那么等价于我们看这个环的时候可以直接跳过(y)。既然原先的环会导出矛盾,那么当前这个环照样会导出矛盾,那么我们重复这个过程,就可以得到自环。而其他的边存在的原因和前面两个证明类似,不再重复证明。

  • 相关阅读:
    poj 2411 Mondriaan's Dream 骨牌铺放 状压dp
    zoj 3471 Most Powerful (有向图)最大生成树 状压dp
    poj 2280 Islands and Bridges 哈密尔顿路 状压dp
    hdu 3001 Travelling 经过所有点(最多两次)的最短路径 三进制状压dp
    poj 3311 Hie with the Pie 经过所有点(可重)的最短路径 floyd + 状压dp
    poj 1185 炮兵阵地 状压dp
    poj 3254 Corn Fields 状压dp入门
    loj 6278 6279 数列分块入门 2 3
    VIM记事——大小写转换
    DKIM支持样本上传做检测的网站
  • 原文地址:https://www.cnblogs.com/cjyyb/p/9683682.html
Copyright © 2011-2022 走看看