BZOJ5318: [Jsoi2018]扫地机器人(DP)

传送门

题解:
考虑朴素DP,我们可以状压来转移。

继续观察性质:
如果是 nn n ∗ n 的方阵,那么副对角线元素相同。
如果是 nm n ∗ m 的方阵,那么设 d=gcd(n,m) d = gcd ( n , m ) ,每个 dd d ∗ d 的方阵都与第一个相同。且副对角线相同。

这意味着我们只需要枚举 dd d ∗ d 的矩阵中向左移动 dx d x ,向下移动了 dy=ddx d y = d − d x 即可确定整个矩形,同时状压也不必要了,因为前 d d 步不会走过重复节点。注意要满足gcd(dx,n),gcd(dy,m)=1,否则无法遍历完矩形,为非法路径。

我们直接对着 dd d ∗ d 的矩形做DP。 枚举以哪个点为终点做路径DP即可。注意有许多点的DP是相同的,我们可以一起做。(也就是这篇博客中的枚举轮数)。

#include <bits/stdc++.h>
using namespace std;
const int RLEN=1<<18|1;
inline int rd(int x=0) {return (scanf("%d",&x),x);}
const int N=55,mod=998244353;
int n,m,st,bl,ans;
int fir[N][N],f[N][N],g[N][N];
char s[N][N];
inline int gcd(int x,int y) {return y ? gcd(y,x%y) : x;}
inline int add(int x,int y) {return (x+y>=mod) ? (x+y-mod) : (x+y);}
inline int mul(int x,int y) {return (unsigned long long)x*y%mod;}
inline void solve() {
    n=rd(),m=rd(),st=gcd(n,m),bl=n*m/st,ans=0;
    for(int i=0;i<n;i++) scanf("%s",s[i]);
    for(int tx=0,ty=st;tx<=st;++tx,--ty) if(gcd(tx,n)==1 && gcd(ty,m)==1) {
        memset(fir,0x3f,sizeof(fir));
        for(int i=1,stx=0,sty=0;i<=bl;i++,stx=(stx+tx)%n,sty=(sty+ty)%m) 
            for(int dx=0;dx<=tx;++dx) for(int dy=0;dy<=ty;++dy) 
                if(s[(stx+dx)%n][(sty+dy)%m]=='1') fir[dx][dy]=min(fir[dx][dy],i);
        for(int t=1;t<=bl;t++) {
            memset(f,0,sizeof(f)); memset(g,0,sizeof(g)); f[0][0]=1; g[tx][ty]=1;
            for(int i=0;i<=tx;i++)
                for(int j=0;j<=ty;j++) {
                    if(i && fir[i-1][j]>t) f[i][j]=add(f[i][j],f[i-1][j]);
                    if(j && fir[i][j-1]>t) f[i][j]=add(f[i][j],f[i][j-1]);
                }
            for(int i=tx;i>=0;i--)
                for(int j=ty;j>=0;j--) {
                    if(i<tx && fir[i+1][j]>=t) g[i][j]=add(g[i][j],g[i+1][j]);
                    if(j<ty && fir[i][j+1]>=t) g[i][j]=add(g[i][j],g[i][j+1]);
                }
            for(int i=0;i<=tx;i++)
                for(int j=0;j<=ty;j++) if((i+j)&&fir[i][j]==t)
                    ans=add(ans,mul(mul(f[i][j],g[i][j]),(t-1)*st+i+j));
        }
    } cout<<ans<<'\n';
}
int main() {
    for(int i=rd();i;i--) solve();
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值