码迷,mamicode.com
首页 > 其他好文 > 详细

nikkei2019_2_qual_f Mirror Frame

时间:2020-07-08 13:29:48      阅读:61      评论:0      收藏:0      [点我收藏+]

标签:tps   ++   space   位置   efi   lin   amp   def   正方形   

nikkei2019_2_qual_f Mirror Frame

https://atcoder.jp/contests/nikkei2019-2-qual/tasks/nikkei2019_2_qual_f

在二维平面内,有一个正方形区域,顶点分别为 \((0,0),(0,N),(N,0),(N,N)\) .

光线可以在这个区域内反射,且满足入射角等于出射角.

对于严格在区域内的点 \((x,y),0 < x < N, 0 < y < N\) ,定义它的路径为

  • \((x,y)\) 出发,向 \((x+1,y+1),(x-1,y-1),(x+1,y-1),(x-1,y+1)\) 射出的四条光线的路径的并集

对于每个严格在区域内的位置,存在一个灯泡,初始状态为打开或者关闭.

每次可以选择一个严格在区域内的点,然后翻转所有在它的路径上的灯泡的状态.

现在有一些灯泡的初始状态还未确定,问有多少种确定这些灯泡的方案,满足最后存在一种操作序列使得所有灯泡关闭.

答案对 \(998244353\) 取模

\(2 \le N \le 1500\)

Tutorial

考虑如何判断一种状态是否合法

首先,沿对角线对称的灯泡的状态一定要相等.

一个灯泡的路径上所有的灯的坐标的 \(x+y\) 奇偶性相等,所以将按 \(x+y\) 分类考虑

现在问题可以转化为

\(n\) 个点的完全图,每条边有边权 \(0\)\(1\) .每次可以选择两个点,并翻转所有至少和这两个点中的一个相邻的边的状态.

问是否可以将所有边的边权变为 \(0\)

如果我们找到一个回路 \((x_1,x_2,\cdots,x_k=x_1)\) ,并依次翻转 \((x_1,x_2),(x_2,x_3),\cdots,(x_{k-1},x_k)\) ,那么状态改变的边就是 \((x_1,x_2),(x_2,x_3),\cdots,(x_{k-1},x_k)\) .也就是说,存在合法方案的充分条件为图中的 \(1\) 边存在欧拉回路.

假如 \(n\) 为偶数,那么如果我们翻转两个点 \((a,b)\) ,那么 \(a,b\) 相邻的 \(1\) 边度数的奇偶性发生变化,所以可以通过操作使得所有点的度数为偶数.所以当 \(n\) 为偶数的时候一定合法.

假如 \(n\) 为奇数,那么翻转两个点 \((a,b)\) 时, \(a,b\) 的度数奇偶性不会发生变化(同时证明了必要性),所以必须要满足每个点的相邻的 \(1\) 边数为偶数.

考虑求方案

\(n\) 个点(\(n\) 为奇数)的完全图,每条边的边权 \(0\)\(1\)\(-1\), 对于所有的 \(-1\) 边分配 \(0\)\(1\) 的边权,使得所有点相邻的 \(1\) 边数为偶数

对于每个 \(-1\) 边联通块, \(1\) 边度数为奇数的点必须是偶数个,否则无解.

方案数就是 \(-1\) 边数减去树边个数(\(n\) 减去联通块个数)

Code

#include <cmath> 
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#define debug(...) fprintf(stderr,__VA_ARGS__)
using namespace std;
template<class T> void rd(T &x) {
	x=0; int f=1,ch=getchar();
	while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)f=-1;ch=getchar();}
	while(ch>=‘0‘&&ch<=‘9‘){x=x*10-‘0‘+ch;ch=getchar();}
	x*=f;
}
typedef long long ll;
const int mod=998244353;
const int maxn=1500+5;
int n; char A[maxn][maxn];
int head[maxn];
int adj[maxn][maxn],deg[maxn];
int vis[maxn],E,P,C;
struct edge {
	int to,nex;
	edge(int to=0,int nex=0):to(to),nex(nex){}
};
vector<edge> G;
inline int add(int x) {return x>=mod?x-mod:x;}
ll power(ll x,ll y) {
	ll re=1;
	while(y) {
		if(y&1) re=re*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return re;
}
inline void addedge(int u,int v) {
	G.push_back(edge(v,head[u])),head[u]=G.size()-1;
	G.push_back(edge(u,head[v])),head[v]=G.size()-1;
}
inline int idx0(int x,int y) {return x+y;}
inline int idx1(int x,int y) {return x<y?idx0(0,y-x):idx0(x-y,0);}
inline bool upd(int x,int y,char c) {
	if(c==‘?‘) return 1;
	int a=x+y,b=x-y;
	if(a>n) a=2*n-a;
	if(b<0) b=-b;
	x=(a+b)/2,y=a-x;
	a=idx0(x,y),b=idx1(x,y);
	if(a>b) swap(a,b);
	if(adj[a][b]==-1) {
		adj[a][b]=c==‘x‘; 
		deg[a]^=adj[a][b];
		deg[b]^=adj[a][b];
	}
	else return adj[a][b]==(c==‘x‘);
	return 1;
}
void travel(int u) {
	if(vis[u]) return; vis[u]=1;
	++P; if(deg[u]) ++C;
	for(int i=head[u];~i;i=G[i].nex) {
		int v=G[i].to;
		++E;
		travel(v);
	}
}
int sol(int k) {
	for(int i=1;i<n;++i) for(int j=1;j<n;++j) if(((i+j)&1)==k) {
		if(!upd(i,j,A[i][j])) return 0;
	}
	int cnt=0;
	for(int i=k;i<=n;i+=2) ++cnt;
	if(~cnt&1) {
		int an=1;
		for(int i=k;i<=n;i+=2) for(int j=i+2;j<=n;j+=2) {
			if(adj[i][j]==-1) an=add(an<<1);
		}
		return an;
	}
	for(int i=k;i<=n;i+=2) for(int j=i+2;j<=n;j+=2) {
		if(adj[i][j]==-1) addedge(i,j);
	}
	int an=1;
	for(int i=k;i<=n;i+=2) if(!vis[i]) {
		E=0,P=0,C=0,travel(i),E>>=1;
		if(C&1) return 0;
		an=an*power(2,E-(P-1))%mod;
	}
	return an;
}
int main() {
	rd(n);
	for(int i=1;i<n;++i) scanf("%s",A[i]+1);
	memset(adj,-1,sizeof(adj));
	memset(head,-1,sizeof(head));
	printf("%lld\n",(ll)sol(0)*sol(1)%mod);
	return 0;
}

nikkei2019_2_qual_f Mirror Frame

标签:tps   ++   space   位置   efi   lin   amp   def   正方形   

原文地址:https://www.cnblogs.com/ljzalc1022/p/13266208.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!