[JSOI2018]机器人(数论+dp)
题目:洛谷P4558、LOJ#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\)转换成\(gcd\)的形式,可以化简得到
所以
现在令\(gcd(n,x) \geq 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,m)\),即
所以
所以
必要性证明完毕
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;
}

浙公网安备 33010602011771号