神仙题(为什么就没能自己想出来呢/zk/zk)
这是我 AC 的第 \(2\times 10^3\) 道题哦
首先考虑 \(m=2\) 的情况,我们首先可以想到一个非常 trivial 的 DP:\(dp_i\) 表示填好前 \(i\) 列的方案数,那么第 \(i\) 列显然有横着放和竖着放两种可能,方案数分别是 \(dp_{i-2}\) 和 \(dp_{i-1}\),因此我们有 \(dp_i=dp_{i-2}+dp_{i-1}\),边界条件 \(dp_0=1\),显然这个递推式与斐波那契数列有非常紧密的联系,具体来说 \(dp_i=f_{i+1}\)。因此我们可以将 \(L,R\) 分别加一,那么答案可以表示成:
\]
一脸不好用正常方法维护的样子。
不过注意到对于斐波那契数列而言我们有一个通项公式 \(f_i=\dfrac{1}{\sqrt{5}}((\dfrac{1+\sqrt{5}}{2})^n-(\dfrac{1-\sqrt{5}}{2})^n)\),因此我们考虑在这个通项公式上做点手脚。方便起见下文中记 \(A=\dfrac{1}{\sqrt{5}},B=-\dfrac{1}{\sqrt{5}},X=\dfrac{1+\sqrt{5}}{2},Y=\dfrac{1-\sqrt{5}}{2}\),那么 \(f_i=AX^i+BY^i\)。有人会说,这 \(\sqrt{5}\) 无理数都出来了,况且 \(\sqrt{5}\) 还在组合数上指标上,那不是更不好维护了吗?别急,\(\sqrt{5}\) 这样的无理数确实和阶乘幂不太搭,不过别忘了我们还有个阶乘幂转下降幂的公式:\(x^{\underline{k}}=\sum\limits_{i=0}^k\begin{bmatrix}k\\i\end{bmatrix}(-1)^{k-i}x^i\),因此 \(\dbinom{f_i}{k}=\dbinom{1}{k!}f_i^{\underline{k}}=\dfrac{1}{k!}\sum\limits_{j=0}^k\begin{bmatrix}k\\j\end{bmatrix}(-1)^{k-j}f_i^j\),因此我们有:
ans&=\sum\limits_{i=L}^R\dbinom{f_i}{k}\\
&=\dfrac{1}{k!}\sum\limits_{i=L}^R\sum\limits_{j=0}^k\begin{bmatrix}k\\j\end{bmatrix}(-1)^{k-j}f_i^j\\
&=\dfrac{1}{k!}\sum\limits_{i=L}^R\sum\limits_{j=0}^k\begin{bmatrix}k\\j\end{bmatrix}(-1)^{k-j}(AX^i+BY^i)^j\\
&=\dfrac{1}{k!}\sum\limits_{i=L}^R\sum\limits_{j=0}^k\begin{bmatrix}k\\j\end{bmatrix}(-1)^{k-j}\sum\limits_{p=0}^j\dbinom{j}{p}(AX^i)^p(BY^i)^{j-p}\\
&=\dfrac{1}{k!}\sum\limits_{j=0}^k\begin{bmatrix}k\\j\end{bmatrix}(-1)^{k-j}\sum\limits_{p=0}^j\dbinom{j}{p}A^pB^{j-p}\sum\limits_{i=L}^R(X^pY^{j-p})^i
\end{aligned}
\]
前面都可以在 \(\mathcal O(k^2)\) 时间内枚举得出,后面可以一波等比数列求和公式带走,时间复杂度 \(k^2\log R\)。
有几个细节需要注意:
- 注意特判 \(X^pY^{j-p}=1\) 的情况
- 由于 \(5\) 在 \(\bmod 998244353\) 意义下无二次剩余(凉 心 出 题 人),因此我们需要像二次剩余那样将数域由 \(\mathbb{R}\) 扩展到 \(\mathbb{Z}_5=\{a+b\sqrt{5}|a,b\in\mathbb{R}\}\)(瞎起名字 ing),加减乘除照样定义,这样就可以避免这个问题了。
这个故事告诉我们,有时候遇到斐波那契数列有关的题,即使模数不那么友好也能套通项公式/ts
接下来考虑 \(m=3\),首先 \(n\) 是奇数的情况答案肯定是 \(0\),我们 duck 不必管他们。对于 \(n\) 是偶数的情况咱们还是先从最 naive 的 DP 入手,那么最后一列可以三个横着的 \(1\times 2\) 多米诺骨牌,方案数 \(dp_{i-2}\),也可以是第一行放一个横着的骨牌,下面两列各放一个竖着的骨牌,当然也可以 reverse 一下,横着的骨牌放在第三行,方案数 \(2dp_{i-2}\),还可以第一行来一个横着的骨牌,紧接着下面跟着一个竖着的骨牌,紧接着左边又是 \(2k(k\in[1,\infty)\cap\mathbb{Z})\) 个横着的骨牌,最后又一个竖着的,那么此时应从 \(dp_{i-2k-2}\) 转移过来,方案数 \(2dp_{i-2k-2}\),因此我们有
\]
前缀和搞搞可以变成:
\]
如果我们设 \(g_n=dp_{2n}\),那么上式可写作 \(g_i=4g_{i-1}-g_{i-2}\)。
喜闻乐见的 \(a_i=Aa_{i-1}+Ba_{i-2}\) 的形式,列出特征根方程 \(1=4x-x^2\),解出它的两个根为 \(x_1=2+\sqrt{3},x_2=2-\sqrt{3}\),然后套个待定系数法,设 \(g_i=Ax_1^i+Bx_2^i\),那么:
A+B=1\\
(2+\sqrt{3})A+(2-\sqrt{3})B=3
\end{cases}
\]
解得
A=\dfrac{3+\sqrt{3}}{6}\\
B=\dfrac{3-\sqrt{3}}{6}
\end{cases}
\]
然后就和 \(m=2\) 的情况一样了。
然鹅由于太久没有接触普通幂与阶乘幂之间的转换,一开始甚至把第一类斯特林数敲成了第二类斯特林数……这里稍微澄清一下:
- 用阶乘幂表示普通幂用第二类斯特林数,以等式 \(x^k=\sum\limits_{i=0}^k\begin{Bmatrix}k\\i\end{Bmatrix}x^{\underline{i}}\) 为例,如果我们把等号左右两边叫做一区和二区,那么下划线在哪个区就用第几类斯特林数。
- 用大幂表示小幂需要加 \((-1)^{k-i}\)
const int MAXK=501;
const int MOD=998244353;
int qpow(int x,int e){
int ret=1;
for(;e;e>>=1,x=1ll*x*x%MOD) if(e&1) ret=1ll*ret*x%MOD;
return ret;
}
int fac[MAXK+5],ifac[MAXK+5],s[MAXK+5][MAXK+5];
void init_fac(int n){
for(int i=(fac[0]=ifac[0]=ifac[1]=1)+1;i<=n;i++) ifac[i]=1ll*ifac[MOD%i]*(MOD-MOD/i)%MOD;
for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i-1]*ifac[i]%MOD;
s[0][0]=1;for(int i=1;i<=n;i++) for(int j=1;j<=i;j++) s[i][j]=(s[i-1][j-1]+1ll*(i-1)*s[i-1][j])%MOD;
}
int sq;
struct comp{
int x,y;
comp(int _x=0,int _y=0):x(_x),y(_y){}
comp operator +(const comp &rhs){return comp((x+rhs.x)%MOD,(y+rhs.y)%MOD);}
comp operator -(const comp &rhs){return comp((x-rhs.x+MOD)%MOD,(y-rhs.y+MOD)%MOD);}
comp operator *(const comp &rhs){return comp((1ll*x*rhs.x+1ll*y*rhs.y%MOD*sq)%MOD,(1ll*x*rhs.y+1ll*y*rhs.x)%MOD);}
comp operator /(const comp &rhs){
ll inv=qpow((1ll*rhs.x*rhs.x%MOD-1ll*sq*rhs.y%MOD*rhs.y%MOD+MOD)%MOD,MOD-2);
return comp(1ll*(1ll*x*rhs.x%MOD-1ll*y*rhs.y%MOD*sq%MOD+MOD)*inv%MOD,1ll*(1ll*y*rhs.x%MOD-1ll*x*rhs.y%MOD+MOD)*inv%MOD);
}
bool operator ==(const comp &rhs){return (!(x^rhs.x)&&!(y^rhs.y));}
} A,B,X,Y;
int binom(int x,int y){return 1ll*fac[x]*ifac[y]%MOD*ifac[x-y]%MOD;}
comp qpow_c(comp x,ll e){
comp res(1);
for(;e;e>>=1,x=x*x) if(e&1) res=res*x;
return res;
}
comp getsum(comp bs,ll t){
if(t<0) return comp();
if(bs==comp(1)) return comp((t+1)%MOD,0);
return (qpow_c(bs,t+1)-comp(1))/(bs-comp(1));
}
comp getsum(comp bs,ll l,ll r){return getsum(bs,r)-getsum(bs,l-1);}
int solve(ll l,ll r,int k){
if(l>r) return 0;comp res;
for(int j=0;j<=k;j++){
comp sum=comp();
for(int p=0;p<=j;p++){
comp mul(1);
mul=mul*comp(binom(j,p))*qpow_c(A,p)*qpow_c(B,j-p);
mul=mul*getsum(qpow_c(X,p)*qpow_c(Y,j-p),l,r);
sum=sum+mul;
} sum=sum*comp(s[k][j]);
// printf("%d %d\n",sum.x,sum.y);
if((k-j)&1) res=res-sum;
else res=res+sum;
} return 1ll*res.x*ifac[k]%MOD;
}
int main(){
int qu,opt;scanf("%d%d",&qu,&opt);init_fac(MAXK);
if(opt==2){
sq=5;A=comp(0,1)/comp(5);B=comp(0,MOD-1)/5;
X=comp(1,1)/comp(2);Y=comp(1,MOD-1)/comp(2);
} else {
sq=3;A=comp(3,1)/comp(6);B=comp(3,MOD-1)/comp(6);
X=comp(2,1);Y=comp(2,MOD-1);
} while(qu--){
ll l,r;int k;scanf("%lld%lld%d",&l,&r,&k);
if(opt==2) printf("%d\n",1ll*qpow((r-l+1)%MOD,MOD-2)*solve(l+1,r+1,k)%MOD);
else printf("%d\n",1ll*qpow((r-l+1)%MOD,MOD-2)*solve(l+1>>1,r>>1,k)%MOD);
}
return 0;
}