JSOI2018 Round2部分题目简要题解
[JSOI2018]列队
一眼题,在可持久化线段树上二分即可.(注意可以直接在线段树上面二分,复杂度是\(O(nlogn)\))
[JSOI2018]机器人
神仙(性质)题,首先我们来找一下如果没有障碍的方案有多少
(以下性质我基本都不会证)
-
\(lemma1:\)
- 每个对角线上的点选的方向都相同.具体证明可以考虑反证,即永远到不了它们都不指向的那个点或者指向那个点两次,这都是不合法的.
-
\(lemma2:\)
- 由\(lemma1\)我们可以得到一个更强的约束条件,即机器人走的步骤是循环的,(因为每走一步都会跳一个对角线,所以最后就是几个对角线反复的走),循环节大小是\(d = gcd(n,m)\),证明的话可以考虑一个\(n * m\)的矩形可以划分成若干个\(d * d\)的方阵,然后在每一个方阵上沿用上面的结论即可
-
\(lemma3:\)
- 也是最神的一个,完全不知道怎么想出来的,考虑由\(lemma2\),对于一个循环节,设走了\(dx\)步向右的,\(dy\)步向下的我们只要确定\(dx,dy\)数量就能确定\(\binom{d}{dx}\)种方案.考虑什么样的\(dx,dy\)是合法的,这里给出一个结论,合法的充要条件是\([gcd(dx,n)== 1][gcd(dy,m) == 1]\).
- 考虑证明,充分性就是考虑因为\(lemma1\),在每一个循环里,你每走一步都会走到一个新的对角线上,考虑假设在\(n*m\)步中,存在\(x_1 + k_1 * dx \equiv x_2 + k_2 * dx(mod\ m) \ \&\& \ y_1 + k_1 * dy \equiv y_2 + k_2 * dy(mod\ n)\),若两个点相同,它们显然在同一对角线上,因为\(lemma1\),循环节内对角线两两不同,所以有\(x_1 == x_2 \ \&\& \ y_1 == y_2\),所以\(k_1 * dx \equiv k_2 * dx(mod\ m)\),\(k_1 * dy \equiv k_2 * dy(mod \ n)\),因为\([gcd(dx,n) == 1][gcd(dy,m) == 1]\),所以有\((k_1 - k_2)|(lcm(n,m))\),又因为\(k1,k2 < \frac{n * m}{gcd(n,m)} = lcm(n,m)\)矛盾,所以充分性得证.
- 必要性可以由
Bezout theory
证明,这里懒得写了 - 有了以上三个\(lemma\)后,这题就变得简单了,注意到在遇到障碍前走了多少步可以等价于遇到最早的障碍前走了多少步,设\(dp[i][j][k]\)表示在循环节内当前走到\((i,j)\),到最早被挡住的障碍走的步数为\(k\)的方案数,预处理每个点在接下里的循环中第一次被障碍挡住的时间,转移是显然的.
- 代码如下:
/*机器人*/
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int read(){
char c = getchar();
int x = 0;
while(c < '0' || c > '9') c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48,c = getchar();
return x;
}
const int N = 55;
int A[N][N];
char s[N][N];
#define mod 998244353
#define inf 0x3f3f3f3f
int dp[N][N][N*N];
int n,m;
int gcd(int x,int y){
if(y == 0) return x;
return gcd(y,x%y);
}
void add(int &x,int y){
x += y - mod;
x += (x >> 31) & mod;
}
void solve(){
int d = gcd(n,m);
int ans = 0;
for(int dx = 1; dx <= d; ++dx){
int dy = d - dx;
if(gcd(dx,dy) == 1 && gcd(dx,n) == 1 && gcd(dy,m) == 1){/*合法*/
for(int i = 1; i <= dx + 1; ++i)
for(int j = 1; j <= dy + 1; ++j) A[i][j] = n * m;
for(int i = 1; i <= dx + 1; ++i)
for(int j = 1; j <= dy + 1; ++j)
for(int k = 0; k <= n * m / d; ++k){
int u = (i + k * dx) % n,v = (j + k * dy) % m;
if(u == 0) u = n;if(v == 0) v = m;
if(s[u][v] == '1') A[i][j] = min(A[i][j],i + j - 2 + k * d);
}
for(int i = 1; i <= dx + 1; ++i)
for(int j = 1; j <= dy + 1; ++j)
for(int k = 1; k <= n * m; ++k) dp[i][j][k] = 0;
dp[1][1][A[1][1]] = 1;
for(int i = 1; i <= dx + 1; ++i){
for(int j = 1; j <= dy + 1; ++j){
for(int k = 1; k <= n * m; ++k){
if(i + 1 <= dx + 1)
add(dp[i+1][j][min(k,A[i+1][j])],dp[i][j][k]);
if(j + 1 <= dy + 1)
add(dp[i][j+1][min(k,A[i][j+1])],dp[i][j][k]);
}
}
}
for(int k = 1; k <= n * m; ++k)
add(ans,1ll * dp[dx+1][dy+1][k] * k % mod);
}
}
printf("%d\n",ans);
}
int main(){
int T = read();
while(T--){
n = read(),m = read();
for(int i = 1; i <= n; ++i) scanf("%s",s[i]+1);
solve();
}
return 0;
}