L O J LOJ LOJ 传送门
-
题解:神仙数数题,从性质入手
-
结论 1:一条副对角线上的方向相同,因为如果不相同的话就会有格子没有走到或是从两个方向走过来
结论 2:第 i i i 条对角线与第 i + g c d ( n , m ) i+gcd(n,m) i+gcd(n,m) 条对角线相同,证明咕
结论 3:有了第 2 个结论,我们可以发现机器人走的路径是循环的,且循环节长度为 g c d ( n , m ) gcd(n,m) gcd(n,m) -
我们不妨设循环节长度为 d d d,其中向下走了 i i i 步向右走了 j j j 步,那么纵坐标回到 1 需要 l c m ( i , n ) lcm(i,n) lcm(i,n)个循环节,横坐标会到原点需要 l c m ( j , m ) lcm(j,m) lcm(j,m) 个循环节,所以有等式
n m g c d ( n , m ) = l c m ( i , n ) ∗ l c m ( j , m ) g c d ( l c m ( i , n ) , l c m ( j , m ) ) \frac{nm}{gcd(n,m)}=\frac{lcm(i,n)*lcm(j,m)}{gcd(lcm(i,n),lcm(j,m))} gcd(n,m)nm=gcd(lcm(i,n),lcm(j,m))lcm(i,n)∗lcm(j,m)
显然成立时当且仅当 l c m ( i , n ) = n , l c m ( j , m ) = m lcm(i,n)=n,lcm(j,m)=m lcm(i,n)=n,lcm(j,m)=m
那么当除 ( 1 , 1 ) (1,1) (1,1) 外的格子全部是 1 时,方案数为
w a y s = ∑ g c d ( i , d ) = 1 , g c d ( i , n ) = 1 , g c d ( j , m ) = 1 ( d i ) ways=\sum_{gcd(i,d)=1,gcd(i,n)=1,gcd(j,m)=1}\binom{d}{i} ways=gcd(i,d)=1,gcd(i,n)=1,gcd(j,m)=1∑(id)
我们考虑 d p dp dp 出有障碍的情况,枚举向下的步数 i i i,那么我们只需要对 ( i + 1 ) ∗ ( j + 1 ) (i+1)*(j+1) (i+1)∗(j+1) 的矩阵讨论每一种走法经过多少步遇到障碍物,注意到遇到一个障碍物时走到步数可以提前预处理,我们把所有障碍物缩到这一个 ( i + 1 ) ∗ ( j + 1 ) (i+1)*(j+1) (i+1)∗(j+1) 的矩阵中,那么问题就是对每一条路径,贡献是路径上的最小值
那么我们统计每一个最小值的出现次数, d p i , j , k dp_{i,j,k} dpi,j,k 表示到 i , j i,j i,j 最小值为 k k k 的出现次数
复杂度 O ( T n 5 ) O(Tn^5) O(Tn5) 但常数很小
#include<bits/stdc++.h>
#define cs const
using namespace std;
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1; }
while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
return cnt * f;
}
cs int Mod = 998244353;
int add(int a, int b){ return a + b >= Mod ? a + b - Mod : a + b; }
int mul(int a, int b){ return 1ll * a * b % Mod; }
void Add(int &a, int b){ a = add(a, b); }
void Mul(int &a, int b){ a = mul(a, b); }
cs int N = 60;
int gcd(int a, int b){ return !b ? a : gcd(b, a%b); }
int T, n, m, mn[N][N]; char mp[N][N];
int work(int dn, int dm){
static int dp[N][N][N*N];
dp[1][1][mn[1][1]]=1;
for(int i=1; i<=dn; i++)
for(int j=1; j<=dm; j++)
for(int k=1; k<=n*m; k++) if(dp[i][j][k]){
if(i+1<=dn) Add(dp[i+1][j][min(k,mn[i+1][j])],dp[i][j][k]);
if(j+1<=dm) Add(dp[i][j+1][min(k,mn[i][j+1])],dp[i][j][k]);
}
int as = 0;
for(int i=1; i<=n*m; i++) Add(as, mul(i,dp[dn][dm][i]));
for(int i=1; i<=dn; i++) for(int j=1; j<=dm; j++)
for(int k=1; k<=n*m; k++) dp[i][j][k]=0;
return as;
}
void Solve(){
n = read(), m = read();
for(int i=1; i<=n; i++) scanf("%s",mp[i]+1);
int d = gcd(n, m), as = 0;
for(int i = 1, j = d-1; i < d; i++, j--){
if(gcd(i,d) == 1 && gcd(i,n) == 1 && gcd(j,m) == 1){
for(int l = 1; l <= i+1; l++)
for(int r = 1; r <= j+1; r++){
int u=l, v=r, stp = l+r-2; mn[l][r] = n*m;
while((u^l)||(v^r)||(stp==l+r-2)){
if(mp[u][v]=='1'){ mn[l][r]=stp; break; }
u+=i; v+=j; stp+=d; if(u>n) u-=n; if(v>m) v-=m;
}
}
Add(as, work(i+1, j+1));
}
} cout << as << '\n';
}
int main(){
T = read();
while(T--) Solve();
return 0;
}