[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\) 开始清空(亲测有效).