[DP套DP] UOJ#141. 【UER #4】量子态的棋盘

题意

棋盘的大小是 n×m,每一个格子的权值都是 1或者−1 。
这个棋盘左上角格子的上方是入口,下边界和右边界是出口。
当 Picks 博士从左上角上方放入一个小球的时候,这个小球在这个棋盘中会以如下的方式运行:
如果当前小球所在的格子的权值是 1,那么小球将会从这个格子的右边界出去,否则它会从这个格子的下边界出去。在小球离开这个格子之后,这个格子的权值会变成原来的相反数,即 1 变成 −1,−1 变成 1。
Picks 博士的实验中的一步是这样的:他会依次往棋盘的左上角放入K个小球,每一个小球都是在前一个小球离开棋盘后再放入。当小球离开棋盘后,它会直接掉到地上。为了避免满地小球的情况, Picks 在一些出口下面挂了篮子,如果小球从挂了篮子的出口处离开棋盘,那么它会被篮子接住,否则它还是会掉到地上。
Picks 博士进行了 Q 次这样的试验,为了避免麻烦,任意两次实验的棋盘大小,放入小球个数,篮子的位置都是完全一样,他只更改了初始棋盘上的数字。但是因为实验结果被打杂的 jiry_2 吃掉了,所以他现在只记得第 i次试验接到的小球数目在区间 [Li,Ri]内。
现在,Picks 博士告诉了你棋盘的大小,放入小球的个数,篮子的位置,以及每一次实验的 Li和 Ri,他想要拜托你算出来每一次试验有多少种可能的棋盘是满足条件的。
n,m<=10 K<=1e+18

题解

其实就是个轮廓线DP啦,强行叫DP套DP...

这个小球数很大,但是注意到如果落在某个网格的小球数为偶数,必然是一半往上一半往下,与格子权值无关,只有当小球数为奇数的时候,最后一个小球的走向才和格子权值有关。
所以我们先推一趟,把不确定走向的小球留在那个位置。这样最多就只有n*m个球了。
然后考虑轮廓线DP,记下这里有几个小球从这个边界位置滚下。
这个状态的数量比较玄学,反正就是能过的。
如何存状态呢?
我参考了Manchery大神的用80进制存轮廓线后Hash(http://blog.csdn.net/u014609452/article/details/72823557),然后跑的飞快

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MOD=998244353, H_MOD=1000007, maxs=500005;
typedef unsigned long long uLL;
typedef long long LL;
uLL B=80,p_B[15];
struct HashMap{
    int hshTab[H_MOD+5],len,clk,tim[H_MOD+5];
    int nxt[maxs],cnt[maxs],res[maxs]; uLL bln[maxs];
    void Clear(){ len=0; clk++; }
    int getHead(int f_h){ return tim[f_h]!=clk?(tim[f_h]=clk,hshTab[f_h]=0):hshTab[f_h]; }
    int &At(uLL _bln,int _cnt){
        int f_h=(_bln*233+_cnt)%H_MOD;
        for(int j=getHead(f_h);j;j=nxt[j]) if(bln[j]==_bln&&cnt[j]==_cnt) return res[j];
        nxt[++len]=hshTab[f_h]; hshTab[f_h]=len;
        bln[len]=_bln; cnt[len]=_cnt; res[len]=0;
        return res[len];
    }
} f[2];
int n,m,Q,sum[15*15];
LL w[15][15],tot;
char out1[15],out2[15];
int main(){
    freopen("uoj141.in","r",stdin);
    freopen("uoj141.out","w",stdout);
    scanf("%d%d%lld%s%s",&n,&m,&w[1][1],out1+1,out2+1);
    for(int i=1;i<=n;i++)
     for(int j=1;j<=m;j++) w[i][j+1]+=w[i][j]/2, w[i+1][j]+=w[i][j]/2, w[i][j]&=1;
    for(int i=1;i<=n;i++) tot+=(out1[i]=='1')*w[i][m+1], w[i][m+1]=0;
    for(int i=1;i<=m;i++) tot+=(out2[i]=='1')*w[n+1][i], w[n+1][i]=0;
    p_B[0]=1; for(int i=1;i<=m;i++) p_B[i]=p_B[i-1]*B;
    int p=0; f[p].Clear();
    uLL t=0; for(int i=m;i;i--) t=t*B+w[1][i]; f[p].At(t,0)=1;
    for(int i=1;i<=n*m;i++,p^=1){
        int x=(i-1)/m+1,y=(i-1)%m+1;
        f[p^1].Clear();
        for (int s=1;s<=f[p].len;s++){
            int now=f[p].bln[s]%B; LL n_bln; int n_cnt;
            n_bln=f[p].bln[s]/B+p_B[m-1]*w[x+1][y]; n_cnt=f[p].cnt[s];
            if (y<m) n_bln+=(now+1)/2;
                else n_cnt+=((now+1)/2)*(out1[x]=='1');
            if (x<n) n_bln+=(now/2)*p_B[m-1];
                else n_cnt+=(now/2)*(out2[y]=='1');
            (f[p^1].At(n_bln,n_cnt)+=f[p].res[s])%=MOD;
            n_bln=f[p].bln[s]/B+p_B[m-1]*w[x+1][y]; n_cnt=f[p].cnt[s];
            if (y<m) n_bln+=(now/2);
                else n_cnt+=(now/2)*(out1[x]=='1');
            if (x<n) n_bln+=((now+1)/2)*p_B[m-1];
                else n_cnt+=((now+1)/2)*(out2[y]=='1');
            (f[p^1].At(n_bln,n_cnt)+=f[p].res[s])%=MOD;
        }
    }
    for (int i=1;i<=f[p].len;i++) (sum[f[p].cnt[i]]+=f[p].res[i])%=MOD;
    for(int i=1;i<=100;i++) (sum[i]+=sum[i-1])%=MOD;
    scanf("%d",&Q);
    while(Q--){
        LL L,R; scanf("%lld%lld",&L,&R); L=max(L-tot,(LL)0); R=min(R-tot,(LL)100);
        if(L>R) printf("0\n"); 
           else printf("%d\n",((sum[R]-(L-1>=0?sum[L-1]:0))%MOD+MOD)%MOD);
    }
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值