E. Dima and Kicks(欧拉图)

题意

给出一个n*m的01矩阵a,求满足下列条件的k:

  • k>1
  • 网格为点,每个网格和它四个方向相邻的网格都存在一条边,从某个 a i j = 1 a_{ij} = 1 aij=1的网格出发,每次只能向上下左右任意一方向移动k个位置,所经过的网格必须都为1,存在某个移动方案,使得边不能重复经过,覆盖矩阵中所有的1。

思路

  • 首先图必须要连通

  • 如果我们确定了一些点,称为拐弯点,在这些点移动出现方向的变换,那么建立一个新图,顶点为这些拐弯点,将位于同一行或者同一列,相邻的两个被1所连接的拐弯点连边,边长就是他们的距离差。如果新图存在欧拉路径或者欧拉回路,那么方案一定存在,那么最大的答案就是边长的gcd。

  • 分析发现如果一个点满足向上下存在至少一个相邻的1 并且左右存在一个相邻的1,它必定为拐弯点

    1 0
    1 1

    0 1 0
    1 1 1

    0 1 0
    1 1 1
    0 1 0
    此外,存在方案的图必定不存在任意如下子矩阵
    1 1
    1 1

原因:
设如果该图存在一个方案,但是它在某个拐弯点不拐弯,继续保持原来方向前进,但是由于该点的度大于2,如果保持方向,必然将从另外一个方向进入该点,形成一个回路,那么将回路倒着跑一遍也是合法的(从改变方向的那个边出去,原方向的边进入)。所以如果一个点满足向上下存在至少一个相邻的1 并且左右存在一个相邻的1,它必定为拐弯点

所以做法只要确定拐弯点,求出所有边长的gcd,然后答案激素gcd的因子扣掉1的情况。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,int>pii;
typedef vector<int>vi;

#define rep(i,a,b) for(int i=(a);i<(b);i++)
#define fi first
#define se second
#define de(x) cout<<#x<<"="<<x<<"\n"
#define dd(x) cout<<#x<<"="<<x<<" "
#define pb(x) push_back(x)
#define all(x) x.begin(),x.end()
#define sz(x) (int)x.size()
#define lowbit(a) ((a)&-(a))
#define per(i,a,b) for(int i=(b)-1;i>=(a);--i)
const int N=3e3+5;
vi ans;
int n ,m;
int a[1004][1005],in[N][N];
bool vis[N*N],use[N][N],lab[N*N];
int d[5][2] ={{1,0},{0,1}};
vector<pii> g[N*N];  //pii<点,边编号> 
int p[N];
int num;
void dfs(int u){
	num++;
	vis[u] = true;
	for(auto it:g[u]){
		if(vis[it.fi])continue;
		dfs(it.fi);
	}
}
inline to(int x,int y){return (x - 1)*m + y;}
int rt = -1;
int l[N][N],h[N][N];
void init(){
	rep(i , 1 , n + 1)
		rep( j , 1 , m + 1){
			if(!a[i][j])continue;
			l[i][j] = l[i][j - 1] + 1;
			h[i][j] = h[i-1][j] + 1;
		}
}
int mx = 0;
void solve(int x){
	if(x == 0)return;
	if(mx == 0 )mx = x;
	else mx = __gcd(mx, x);
}
int main()
{
	cin>>n>>m;
	rep(i , 1 , n + 1)
		rep(j , 1 , m + 1)cin>>a[i][j];
	int tot = 0,ss = 0,odd = 0; 
	init();
	rep(i , 1 , n + 1)
		rep(j , 1 , m + 1){
			if(!a[i][j])continue;
			if(rt == -1)rt = to(i,j);
			rep( k , 0 ,2){
				int nx = i + d[k][0],ny = j + d[k][1];
				if(!a[nx][ny])continue;
				g[to(i,j)].pb(pii(to(nx,ny),++tot));
				g[to(nx,ny)].pb(pii(to(i,j),tot));
			}
			ss++;
			if((a[i + 1][j] || a[i - 1][j]) && (a[i][j + 1]||a[i][j-1]))use[i][j] = true;
			int now = a[i + 1][j] + a[i - 1][j] + a[i][j + 1] + a[i][j-1];
			if(now&1)odd++,use[i][j] = true;
		}
	dfs(rt);
	if(num != ss || odd>2){
		cout<<-1;
		return 0; 
	} 
	 
	rep(i , 1 , n + 1)
		rep( j , 1 , m + 1){
			if(!use[i][j])continue;
			solve(l[i][j] - 1);
			solve(h[i][j] - 1);
		}
	if(mx <= 1)cout<<-1;
	else{
		rep(i , 2 , mx)
			if(mx%i == 0)cout<<i<<" ";
		cout<<mx<<"\n"; 
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值