0
点赞
收藏
分享

微信扫一扫

「JSOI2018」机器人(数数)(DP)

「JSOI2018」机器人(数数)(DP)_循环节 传送门​​

  • 题解:神仙数数题,从性质入手
  • 结论 1:一条副对角线上的方向相同,因为如果不相同的话就会有格子没有走到或是从两个方向走过来
    结论 2:第 「JSOI2018」机器人(数数)(DP)_i++_02 条对角线与第 「JSOI2018」机器人(数数)(DP)_i++_03 条对角线相同,证明咕
    结论 3:有了第 2 个结论,我们可以发现机器人走的路径是循环的,且循环节长度为 「JSOI2018」机器人(数数)(DP)_循环节_04
  • 我们不妨设循环节长度为「JSOI2018」机器人(数数)(DP)_i++_05,其中向下走了 「JSOI2018」机器人(数数)(DP)_i++_02 步向右走了 「JSOI2018」机器人(数数)(DP)_循环节_07 步,那么纵坐标回到 1 需要 「JSOI2018」机器人(数数)(DP)_i++_08个循环节,横坐标会到原点需要 「JSOI2018」机器人(数数)(DP)_循环节_09 个循环节,所以有等式
    「JSOI2018」机器人(数数)(DP)_循环节_10
    显然成立时当且仅当 「JSOI2018」机器人(数数)(DP)_循环节_11
    那么当除 「JSOI2018」机器人(数数)(DP)_循环节_12 外的格子全部是 1 时,方案数为
    「JSOI2018」机器人(数数)(DP)_循环节_13
    我们考虑 「JSOI2018」机器人(数数)(DP)_最小值_14 出有障碍的情况,枚举向下的步数 「JSOI2018」机器人(数数)(DP)_i++_02,那么我们只需要对 「JSOI2018」机器人(数数)(DP)_最小值_16 的矩阵讨论每一种走法经过多少步遇到障碍物,注意到遇到一个障碍物时走到步数可以提前预处理,我们把所有障碍物缩到这一个「JSOI2018」机器人(数数)(DP)_最小值_16 的矩阵中,那么问题就是对每一条路径,贡献是路径上的最小值
    那么我们统计每一个最小值的出现次数,「JSOI2018」机器人(数数)(DP)_最小值_18 表示到 「JSOI2018」机器人(数数)(DP)_最小值_19 最小值为 「JSOI2018」机器人(数数)(DP)_循环节_20 的出现次数
    复杂度 「JSOI2018」机器人(数数)(DP)_最小值_21 但常数很小
#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;
}


举报

相关推荐

0 条评论