[BZOJ3645]Maze

壹、题目

1.1.原链接

传送门

1.2.用我の话说

给一个 \(n\times m\) 的迷宫 \(\tt(maze)\).

入口与第一行的每个房间都有链接,对于第一行的第 \(i\) 个房间,通道数量为 \(a_i\).

对于任意两个房间 \(\lang x,y\rang,\lang u,v\rang\),当且仅当两个房间之间的曼哈顿距离不大于 \(k\) 且处于相邻的两行,房间直接存在通道连接.

对于入口到第一行房间的所有通道,只能通过其从入口走向房间,却没办法反过来走,对于两个房间 \(\lang x,y\rang,\lang u,v\rang\),如果两个房间之间存在连边,只能从行数小的那行走到行数大的那行,而且还要保证走过的房间的列数是单调不递减的.

如果两个有通道相连的房间之间的曼哈顿距离为 \(d\),这两个房间的直接相连通道数目为 \(b_d\).

简要地说,想要从 \(\lang x,y\rang\) 走到 \(\lang u,v\rang\),必须保证有 \(u=x+1,v\ge y\),且从 \(\lang x,y\rang\)\(\lang u,v\rang\) 总共有 \(b_{v-y+1}\) 条通道直接连接.

问题是,从入口出发,到达第 \(n\) 行的第 \(i\) 个房间,总共有多少种走法.

贰、题解

其实就是 \(a\times b^{n-1}\pmod{19}\) 的结果,使用 \(\tt NTT\) 可以很快解决这个问题,需要注意到的问题就是每次 \(\tt NTT\) 过后要变换回来 \(\bmod 19\),防止触及 \(998244353\)\(\tt NTT\) 上界以对答案产生影响.

写这个博客最主要是为了讨论一个问题,这个问题在代码贴出来再进行讨论.

叁、代码

const int mod=998244353;
const int g=3;
const int gi=332748118;
const int maxm=1e5;

inline int qkpow(int a,int n){
    int ret=1;
    for(;n>0;n>>=1,a=1ll*a*a%mod)if(n&1)
        ret=1ll*ret*a%mod;
    return ret;
}

int rev[maxm*4+5],len;
inline void ntt(int* f,const int n,const int opt=1){
    for(int i=0;i<n;++i)if(i<rev[i])
        swap(f[i],f[rev[i]]);
    for(int p=2;p<=n;p<<=1){
        int len=p>>1;
        int w=qkpow(opt==1?g:gi,(mod-1)/p);
        for(int k=0;k<n;k+=p){
            int buf=1,tmp;
            for(int i=k;i<k+len;++i,buf=1ll*buf*w%mod){
                tmp=1ll*buf*f[i+len]%mod;
                f[i+len]=(f[i]+mod-tmp)%mod;
                f[i]=(f[i]+tmp)%mod;
            }
        }
    }
    if(opt==-1){
        int inv=qkpow(n,mod-2);
        for(int i=0;i<n;++i)f[i]=1ll*f[i]*inv%mod;
    }
}

ll n;
int m,k;
int a[maxm*4+5],b[maxm*4+5];

inline void input(){
    n=readin(1ll),m=readin(1),k=readin(1);
    for(int i=0;i<m;++i)a[i]=readin(1);
    for(int i=0;i<k;++i)b[i]=readin(1);
}

inline void prepare(){
    for(len=1;len<=(m<<1);len<<=1);len<<=1;
    for(int i=0;i<len;++i)
        rev[i]=(rev[i>>1]>>1)|((i&1)?(len>>1):0);
}

inline void getans(){
    --n;
    int tot=0;
    for(;n>0;n>>=1){
        ntt(b,len);
        if(n&1){
            ntt(a,len);
            for(int i=0;i<len;++i)
                a[i]=1ll*a[i]*b[i]%mod;
            ntt(a,len,-1);
            for(int i=0;i<m;++i)a[i]%=19;
            for(int i=m;i<len;++i)a[i]=0;
        }
        for(int i=0;i<len;++i)b[i]=1ll*b[i]*b[i]%mod;
        ntt(b,len,-1);
        for(int i=0;i<m;++i)b[i]%=19;
        for(int i=m;i<len;++i)b[i]=0;
    }
    for(int i=0;i<m;++i)writc(a[i],' ');
}

signed main(){
    input();
    prepare();
    getans();
    return 0;
}

肆、讨论の一些小问题

主要讨论为什么要有

for(int i=m;i<len;++i)a[i]=0;

以及

for(int i=m;i<len;++i)b[i]=0;

这两个清空,以及如果没有这个清空会出什么问题.

首先,在理论上来说,由于我们最后使用到的还是 \([0,m)\),是否清空 \([m,len)\) 都对 \([0,m)\) 的系数不产生影响(由生成函数给出),但是不产生影响的前提是进行 \(\tt NTT\) 的过程不会忽略任何项.

但是我们最初设定的多项式长度固定是 \(\tt len\),如果我们不限定数组长度,它在某个时刻会超过 \(\tt len\) 的长度,而这些超过的部分会被忽略,再变换回来就无法将之前的影响对应地去掉,这个时候就要出问题.

也就是说,只要我们能够保证多项式每个时刻长度都不超过 \(\tt len\) 就可以了,所以我们可以不用从 \(m\) 开始清空,甚至可以从 \(\left\lfloor{len\over 2}\right\rfloor\) 开始清空(亲测有效).

posted @ 2021-02-03 10:43  Arextre  阅读(58)  评论(0编辑  收藏  举报