[JSOI2018]机器人(数论+dp)

题目:洛谷P4558LOJ#2550

题目描述:

你有一个大小为\(n*m\)的网格和一个机器人,机器人初始位置在\((1,1)\),机器人每次可以向右或向下行走一步,当机器人在\((n,i)\)向下走时,它会走到\((1,i)\),当机器人在\((i,m)\)向右走时,它会走到\((i,1)\),一种行走方案是合法的当且仅当机器人从\((1,1)\)出发,且将所有格子恰好走一遍,最终回到\((1,1)\),网格中至少有一个格子被标记了(\((1,1)\)不会被标记),一种合法方案的价值为它走的路径上,到达第一个被标记的格子时走的步数,求所有合法方案的总价值

多组数据,数据组数\(T\)

\(T \leq 10\)\(1 \leq n,m \leq 50\)

蒟蒻题解:

定义一个网格的方向为到达它后,往哪个方向走,不难发现,网格\((i-1,j)\)\((i,j-1)\)是同向的,因为他们有且仅有一个是指向\((i,j)\)

紧接着继续猜想推导,定义\((i-1,j+1)\)\((i,j)\)\((i+1,j-1)\)等形式的网格组成一个斜行,容易发现,按从左上到右下的顺序,每\(gcd(n,m)\)个斜行后又是一轮斜行,即第一个斜行和第\(gcd(n,m)+1\)个斜行方向相同,第二个斜行和第\(gcd(n,m)+2\)个斜行方向相同等等,视为同一个循环等价类,接着猜想推导,由于每次只能走到它下一个斜行或和下一个斜行等价的斜行,我们可以发现每\(gcd(n,m)\)步可以构成循环,这里不给具体证明,有兴趣的读者可以自己证一下

想到这一步之后呢?

我模拟考时,考到这题,考场上卡到这一步后,就没再推出其他的东西了,最后也只拿了个\(20\)分的暴力分

还有一个十分重要的性质:假设它一个循环内向下走了\(x\)步,向右走了\(y\)步,\(x+y=gcd(n,m)\),它一定要满足\(gcd(x,n)=1\)\(gcd(y,m)=1\),并且满足此条件的(x,y)一定能符合题意

这句话分成两部分,前面一部分是必要性,后面一部分是充分性,所以它是题意合法方案的充要条件

证明我想了很久,在神仙hx的帮助下证出来(不知道有没有想假,如果假了麻烦告知下),我真是太菜了:

以下假设\(gcd(n,m)=g\),一共走的轮数即为\(\frac{n \cdot m}{g}\),我们先将网格无限延伸开,以免走到边界要判断

1.充分性:

我们可以用反证法,假设它在满足\(gcd(x,n)=1\)\(gcd(y,m)=1\)的前提下,是一个不合法的行走方案

由于它一轮走\(g\)步,走了\(\frac{n \cdot m}{g}\)轮,所以走到的总点数为\(n \cdot m\),又最后一步走到的格子为\((1 + \frac{n \cdot m}{g} \cdot x,1 + \frac{n \cdot m}{g} \cdot y)\)等价于\((1,1)\),所以它最后一定会走到\((1,1)\)

那么如果它不合法,就只能是它走的过程中某些格子重复走,且某些格子漏走了

假设某个格子重复走了多次,由于每次走\(g\)步,是\(g\)个不同的循环等价类,所以对应第一个循环中的一个格子\((a,b)\),每个相同循环等价类的格子可以用\((a + k \cdot x, b + k \cdot y)\)\(0 \leq k < \frac{n \cdot m}{g}\)表示

则这个格子存在两种表示方法:①\((a + k_{1} \cdot x, b + k_{1} \cdot y)\),②\((a + k_{2} \cdot x, b + k_{2} \cdot y)\)

\(a + k_{1} \cdot x \equiv a + k_{2} \cdot x (mod\ n)\)\(b + k_{1} \cdot y \equiv b + k_{2} \cdot y (mod\ m)\)

\(gcd(x,n)=1\)\(gcd(y,m)=1\),所以\(n|(k_{1} - k_{2})\)\(m|{k_{1} - k{2}}\),所以\(lcm(n,m)|{k_{1} - k{2}}\),所以\(\frac{n \cdot m}{g}|{k_{1} - k{2}}\),又\(0 \leq k < \frac{n \cdot m}{g}\),且\(k_{1} \neq k_{2}\),所以假设不成立,所以它在满足\(gcd(x,n)=1\)\(gcd(y,m)=1\)的前提下,一定是一个合法的行走方案,充分性证明完毕

2.必要性:

由于它最后必须回到\((1,1)\)且回到\((1,1)\)是在第\(\frac{n \cdot m}{g}\)轮的最后一步,所以可以得出

\[lcm(n,m)=lcm(\frac{n}{gcd(n,x)}, \frac{m}{gcd(m,y)}) \]

\(lcm\)转换成\(gcd\)的形式,可以化简得到

\[\frac{n \cdot m}{g}=\frac{n \cdot m}{gcd(n,x) \cdot gcd(m,y) \cdot gcd(\frac{n}{gcd(n,x)}, \frac{m}{gcd(m,y)})} \]

所以

\[g=gcd(n,x) \cdot gcd(m,y) \cdot gcd(\frac{n}{gcd(n,x)}, \frac{m}{gcd(m,y)}) \]

现在令\(gcd(n,x) \geq gcd(m,y)\)

由于

\[g=gcd(n,x) \cdot gcd(m,y) \cdot gcd(\frac{n}{gcd(n,x)}, \frac{m}{gcd(m,y)}) \]

所以\(gcd(n,x)|g\),所以\(x|g\),所以\(gcd(n,x)|gcd(n,g)\),所以\(gcd(n,x)|gcd(n,g-x)\)

\(x+y=g\),所以\(gcd(n,x)|gcd(n,y)\),又\(gcd(n,x)|g\),即\(gcd(n,x)|gcd(n,m)\),所以\(gcd(n,x)|gcd(m,y)\)

\(gcd(n,x) \geq gcd(m,y)\),所以\(gcd(n,x) = gcd(m,y)\)

不妨设\(p = gcd(n,x)\),则

\[g=gcd(n,x) \cdot gcd(m,y) \cdot gcd(\frac{n}{gcd(n,x)}, \frac{m}{gcd(m,y)}) \]

可以化成

\[g = p^{2} \cdot gcd(\frac{n}{p}, \frac{m}{p}) \]

\(g = gcd(n,m)\),即

\[g = p \cdot gcd(\frac{n}{p}, \frac{m}{p}) \]

所以

\[p=1 \]

所以

\[gcd(n,x)=gcd(m,y)=1 \]

必要性证明完毕

dp

结论推出来了,接下去就是暴力\(dp\)了,我们首先可以枚举一下\(x\),那么\(y=g-x\),也能求出\(y\)来,再枚举第一次碰到网格里数字为\(1\)的是在第几个循环的哪一个位置,如果这是第一次碰到数字为\(1\)的,那么说明之前都没有碰到过数字为\(1\)的网格

我们现在枚举到已经循环的次数\(t\),这一次循环的开头为\((s_{x}, s_{y})\),我们可以设\(f[x][y]\)表示对于前\(t\)个循环(包含\(t\))内,在第\(t\)个循环开始往下走到\((s_{x}+x, s_{y}+y)\),且目前没有碰到过含有\(1\)的方格的方案数,设\(g[x][y]\)表示对于前\(t-1\)个循环(不包含\(t\)),对于一个循环中的第\((x,y)\)位,每次走到循环的结尾,且不经过含有\(1\)的方格的方案数,那么答案就是\((f[x-1][y]+f[x][y-1]) \cdot g[x][y]\)

枚举\(x\)需要\(\Theta(n)\),枚举\(t\)需要\(\Theta(n)\)\(dp\)需要\(\Theta(n^{2})\),一共\(T\)组数据,所有时间复杂度是\(\Theta(T\cdot n^{4})\)

参考程序:

#include<bits/stdc++.h>
using namespace std;
#define Re register int
typedef long long ll;

const int p = 998244353;
int T, n, m, gd, f[55][55], g[55][55];
ll ans;
bool pd, b[55][55], h[55][55];

inline int read()
{
	char c = getchar();
	int ans = 0;
	while (c < 48 || c > 57) c = getchar();
	while (c >= 48 && c <= 57) ans = (ans << 3) + (ans << 1) + (c ^ 48), c = getchar();
	return ans;
}

inline void write(int x)
{
	if (!x)
	{
		puts("0");
		return;
	}
	int num = 0;
	char sc[15];
	while (x) sc[++num] = x % 10 + 48, x /= 10;
	while (num) putchar(sc[num--]);
	putchar('\n');
}

inline int gcd(int x, int y)
{
	return (y) ? gcd(y, x % y) : x;
}

inline void sol(int id, int sx, int sy, int x, int y)
{
	f[0][0] = g[x][y] = 1;
	for (Re i = 0; i <= x; ++i)
		for (Re j = 0; j <= y; ++j)
		{
			if (!i && !j) continue;
			int u = sx + i, v = sy + j;
			if (u > n) u -= n;
			if (v > m) v -= m;
			f[i][j] = 0;
			if (h[i][j]) continue;
			if (i) f[i][j] = f[i - 1][j];
			if (j) f[i][j] += f[i][j - 1];
			if (f[i][j] >= p) f[i][j] -= p;
			if (b[u][v]) ans = (1ll * f[i][j] * g[i][j] % p * ((id - 1) * gd + i + j) % p + ans) % p, f[i][j] = 0, h[i][j] = 1;
		}
	if (!f[x][y])
	{
		pd = 1;
		return;
	}
	for (Re i = x; i >= 0; --i)
		for (Re j = y; j >= 0; --j)
		{
			if (i == x && j == y) continue;
			int u = sx + i, v = sy + j;
			if (u > n) u -= n;
			if (v > m) v -= m;
			g[i][j] = 0;
			if (h[i][j]) continue;
			if (i < x) g[i][j] = g[i + 1][j];
			if (j < y) g[i][j] += g[i][j + 1];
			if (g[i][j] >= p) g[i][j] -= p;
		}
}

int main()
{
	T = read();
	while (T--)
	{
		n = read(), m = read(), gd = gcd(n, m), ans = 0;
		for (Re i = 1; i <= n; ++i)
			for (Re j = 1; j <= m; ++j)
			{
				char c = getchar();
				while (c ^ 48 && c ^ 49) c = getchar();
				b[i][j] = c ^ 48;
			}
		for (Re i = 1; i < gd; ++i)
		{
			if (gcd(i, n) > 1 || gcd(gd - i, m) > 1) continue;
			g[i][gd - i] = 1;
			for (Re j = i; j >= 0; --j)
				for (Re k = gd - i; k >= 0; --k)
				{
					h[j][k] = 0;
					if (j == i && k == gd - i) continue;
					g[j][k] = 0;
					if (j < i) g[j][k] = g[j + 1][k];
					if (k < gd - i) g[j][k] += g[j][k + 1];
					if (g[j][k] >= p) g[j][k] -= p;
				}
			int sx = 1, sy = 1;
			pd = 0;
			for (Re j = 1; j <= n * m / gd; ++j)
			{
				sol(j, sx, sy, i, gd - i);
				if (pd) break;
				sx += i, sy += gd - i;
				if (sx > n) sx -= n;
				if (sy > m) sy -= m;
			}
		}
		write(ans);
	}
	return 0;
}
posted @ 2021-03-09 16:51  clfzs  阅读(228)  评论(0)    收藏  举报