[JSOI2018]机器人

题目描述#

一个n×m的网格,有一个机器人一开始在(1,1),每次机器人可以向右或向下走一步,(i,m)的右边是(i,1),(n,j)的下边是(1,j),机器人需要不重不漏地走完所有格子回到(1,1)

现在给出若干障碍,对于每一个上述行走方案,走到障碍时会停止,求所有方案的行走的格子的个数和。

解题思路#

首先考虑什么样的方案是合法的。

  • 性质一:对于一个格子(i,j)(i1,j)(i,j1)的行走方向必须是一样的,否则,要么(i,j)会走两次,要么走不到(i,j)

推论:每条副对角线的方向是一样的

  • 性质二:第i条副对角线的方向和第i+gcd(n,m)条的方向是一样的

证明:

把性质一推广到模意义下,副对角线也推广到模意义下,那么(i,m)(i1,1)方向应该是一样的,也就是第i+m1条对角线和第i1条对角线方向一样,也就是差为m的对角线方向一样,同理可得差为n的对角线也一样,那么量条对角线x,y方向一样当且仅当存在a,b使得x+an+bm=y,也就是an+bm=(yx),由裴蜀定理,该式成立当且仅当gcd(n,m)|(yx),也就是说x和x+gcd(n,m)方向一样。

推论:如果把行走的方向看成一个序列,则序列存在一个循环节是gcd(n,m),原因是每一步必定会到下一个对角线,所以该序列和对角线是一样的。

设在一个循环节中向下走了dx步,向右走了dy步,即dx+dy=gcd(n,m)

那么横坐标回到1需要经过lcm(dx,n)dx个循环节,纵坐标回到1需要经过lcm(dy,m)dy个循环节,两个坐标都回到1需要lcm(lcm(dx,n)dx,lcm(dy,m)dy)个循环节,而总共有nmgcd(n,m)个循环节。

也就是说,我们要让

lcm(lcm(dx,n)dx,lcm(dy,m)dy)=nmgcd(n,m)

lcm(ngcd(dx,n),mgcd(dy,m))=lcm(n,m)

对一个质因子p,及其在n,m的指数c1,c2,讨论:

  • min(c1,c2)=0

p|gcd(dx,n),则c1>0,那么左式一定会比比右式在p的指数上少,所以pgcd(dx,n),同理pgcd(dy,m)

  • min(c1,c2)>0

pgcd(dx,n),则p|gcd(n,m)dxp|dy,即p|gcd(dy,m),所以左边还是一定比右边少指数,所以pgcd(dx,n),gcd(dy,m)

综上所述 ,pgcd(dx,n),gcd(dy,m).

gcd(dx,n)=gcd(dy,m)=1,易得这是路径合法的充要条件。

因此可以枚举合法的dx

计算答案时,考虑枚举撞到障碍的轮数,以及撞到的障碍,因为有循环节,所以所有循环节里州的路线是一样的,也就是说,如果第一轮走到了(x,y)这个点,那么之后所有循环节里到这个位置时都必须能走。

方便起见,我们可以把所有循环节的障碍重叠起来,设前i个循环的障碍重叠起来得到的图是Gi

那么我们在第c轮撞到(x,y)的充要条件是:

1.该路线在Gc1可以从左上走到右下。

2.该路线在Gc可以从左上走到(x1,y)(x,y1)

这又等价于:

1.该路线在Gc可以从左上走到(x1,y)(x,y1)

2.该路线在Gc1可以从(x,y)走到右下。

因此我们只需要知道在GcGc1中从左上走到某个点,或从某个点走到右下的方案数即可,显然可以O(nm)dp出来。

因此总的复杂度是O(Tn4),显然没有一维能够跑满,所以可以轻松通过。

#include<bits/stdc++.h>
using namespace std;
const int N =55;
const int mod = 998244353;
char s[N][N];
int f[N][N],g[N][N];
int gcd(int a,int b)
{
	if(!b)return a;
	return gcd(b,a%b);
}
int ans=0;
bool A[N][N],B[N][N];
void get(int n,int m)
{
	for(int i=0;i<=n+1;i++)
	for(int j=0;j<=m+1;j++)
	f[i][j]=g[i][j]=0;
	f[1][1]=(A[1][1]==0);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	{
		if((i==1&&j==1)||A[i][j])continue;
		f[i][j]=(f[i-1][j]+f[i][j-1])%mod;
	}
	g[n][m]=(B[n][m]==0);
	for(int i=n;i>=1;i--)
	for(int j=m;j>=1;j--)
	{
		if((i==n&&j==m)||B[i][j])continue;
		g[i][j]=(g[i+1][j]+g[i][j+1])%mod;
	}
}
#define R(x) (x%m+1)
#define D(x) (x%n+1)
int n,m;
void solve()
{
	ans=0;
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
	int d=gcd(n,m),C=n*m/d;
	for(int dx=0;dx<=d;dx++)
	if(gcd(dx,n)==1&&gcd(d-dx,m)==1)
	{
		int dy=d-dx;
		for(int i=1;i<=dx+1;i++)
		for(int j=1;j<=dy+1;j++)
		A[i][j]=B[i][j]=0;
		int x=1,y=1;
		for(int c=1;c<=C;c++)
		{
			for(int i=x,a=1;a<=dx+1;i=D(i),a++)
			for(int j=y,b=1;b<=dy+1;j=R(j),b++)
			A[a][b]|=(s[i][j]=='1');
			get(dx+1,dy+1);
			for(int i=x,a=1;a<=dx+1;i=D(i),a++)
			for(int j=y,b=1;b<=dy+1;j=R(j),b++)
			if(s[i][j]=='1')ans=(ans+1ll*((c-1)*(dx+dy)+a-1+b-1)%mod*(f[a-1][b]+f[a][b-1])%mod*g[a][b]%mod)%mod;
			for(int i=x,a=1;a<=dx+1;i=D(i),a++)
			for(int j=y,b=1;b<=dy+1;j=R(j),b++)
			B[a][b]|=(s[i][j]=='1');
			for(int a=1;a<=dx;x=D(x),a++);
			for(int b=1;b<=dy;y=R(y),b++);
		}
	}
	printf("%d\n",ans);
}
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		solve();
	}
	return 0;
}
posted @   Larunatrecy  阅读(66)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示
主题色彩