Atcoder Grand Contest 006

Rotate 3x3

题目描述

点此看题

解法

首先观察到一个性质:操作存在可逆性,换句话说就是如果存在解,那么从任意可达的状态出发都可以得到解。

\(\tt Oneindark\):根据这个性质我们可以尝试调整使得只有极小部分不合法,最后再解决它们。

为了简化问题我们把每一列看成代表数字(也就是中间列的数字)和颜色(上下是顺序还是反序:白\(/\)黑),这转化成了一个长度为 \(n\) 的排列问题。

那么我们先从前往后调整,首先让当前位置的数字对上,然后可以通过下面的方法调整当前位置的颜色:a b c d->[c] [b] [a] d->[c] [d] a b->[a] d c b

所以最后只会剩下三个数颜色和数字待考虑,可以考察有解的必要条件:奇数位置和偶数位置的黑点个数都是偶数。由于以偶数位置为中心的交换只会改变偶数位置黑点个数的奇偶性,我们可以把初始的奇偶性记录下来,再异或上交换次数,就得到了最后偶数位置黑点的奇偶性(奇数位置也同理)

然后我们再尝试在这个必要条件的基础上构造,发现按照下列方法一定能构造出来:

3 4 [5] 6 [7] -> 5 [4] [3] 6 [7] -> 5 [4] 7 [6] 3
-> 5 6 [7] 4 3 -> 5 6 [3] [4] 7 -> 3 [6] [5] [4] 7 -> 3 4 5 6 7

所以一开始做完一些琐碎的判断之后,有解的充要条件就是 初始偶数位置黑点数^奇数位置逆序对=1 & 初始奇数位置黑点数^偶数位置逆序对=1

#include <cstdio>
#include <cstring>
const int M = 100005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,a[4][M],b[M],cnt[2],inv[2];
int Abs(int x)
{
	return x>0?x:-x; 
}
int lowbit(int x)
{
	return x&(-x);
}
void add(int x)
{
	for(int i=x;i<=n;i+=lowbit(i))
		b[i]^=1;
}
int ask(int x)
{
	int r=0;
	for(int i=x;i>0;i-=lowbit(i))
		r^=b[i];
	return r;
}
signed main()
{
	n=read();
	for(int i=1;i<=3;i++)
		for(int j=1;j<=n;j++)
			a[i][j]=read();
	for(int i=1;i<=n;i++)
	{
		if(Abs(a[2][i]-a[1][i])!=1 
		|| Abs(a[2][i]-a[3][i])!=1
		|| a[2][i]%3!=2
		|| ((a[2][i]+1)/3)%2!=i%2)
			{puts("No");return 0;}
		if(a[1][i]>a[3][i]) cnt[i&1]++;
	}
	for(int w=0;w<2;w++)
	{
		memset(b,0,sizeof b);
		for(int i=n;i>=1;i--) if(i%2==w)
		{
			int w=(a[2][i]+1)/3;
			inv[i&1]+=ask(w);add(w);
		}
	}
	if((inv[0]^cnt[1])%2 || (inv[1]^cnt[0])%2)
		puts("No");
	else puts("Yes");
}

Blackout

题目描述

点此看题

给定 \(n\) 个点 \(m\) 条边构成的有向图,如果 \((x,y),(y,z)\) 都有边,那么我们可以连边 \((z,x)\),请问最后的图最多有多少条边。

\(n,m\leq 10^5\)

解法

打出暴搜之后发现答案很容易就变成完全图,并且我们发现 \(len\bmod 3=0\) 的环不是完全图,但是 \(len\bmod 3\not=0\) 的环是完全图。我们可以给出合理的猜测:如果对原图三染色失败,那么最后可以得到完全图,有向图三染色的含义是:如果 \((u,v)\) 有边,那么 col[v]=(col[u]+1)%3

首先我们考察另一个东西,如果对原图三染色成功,那么可以把原图分成三层,只有 i,(i+1)%3 这两层之间才连边。考虑证明引理:i,(i+1)%3 层间的边是连满的(我们称之为完全分层图)。

证明考虑归纳法,每次向完全分层图添加一个点,就像下图,我们先添加绿色的边,再添加蓝色的边得到新的完全分层图:

那么如果有一个点的加入使得图不能分层,一定会产生重边(也就是 (u,v),(v,u)),进而产生自环,所以一定会产生完全图(这里把 \(3\) 个点的所有情况讨论一下就可以证明了)

那么直接用结论做就可以了,时间复杂度 \(O(n)\)

#include <cstdio>
const int M = 100005;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,fl,cn,ce,tot,ans,f[M],c[3],vis[M],col[M];
struct edge{int v,c,next;}e[M<<1];
void dfs(int u)
{
	c[col[u]]++;vis[u]=1;cn++;
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v,w=(col[u]+e[i].c+3)%3;
		ce+=(e[i].c==1);
		if(!vis[v]) col[v]=w,dfs(v);
		else if(col[v]!=w) fl=1;
	}
}
signed main()
{
	n=read();m=read();
	for(int i=1;i<=m;i++)
	{
		int u=read(),v=read();
		e[++tot]=edge{v,1,f[u]},f[u]=tot;
		e[++tot]=edge{u,-1,f[v]},f[v]=tot;
	}
	for(int i=1;i<=n;i++)
	{
		if(vis[i]) continue;
		c[0]=c[1]=c[2]=fl=cn=ce=0;
		dfs(i);
		if(fl) ans+=cn*cn;
		else if(c[0] && c[1] && c[2])
			ans+=c[0]*c[1]+c[1]*c[2]+c[2]*c[0];
		else ans+=ce;
	}
	printf("%lld\n",ans);
}
posted @ 2022-01-28 17:02  C202044zxy  阅读(45)  评论(0编辑  收藏  举报