题解:P5320 [BJOI2019] 勘破神机
OrientDragon · · 题解
upd on 2026/4/28:修改了一下
题解区的阅读门槛都有点高,来篇适合中国宝宝体质的题解。
想要阅读本文,你需要有:
- 一些代数运算、思维能力;
- 高中范围的数学知识。
以下正文。
我们从总的
基本递推式
对于 m=2
考虑这部分是斐波那契数列的经典结论。
设方案数为
感性理解就是考虑最后一列两个格子是横放还是竖放。
:::success[特征方程介绍,可跳过]
这个东西的通项公式怎么求呢?我们先忽略初值,假设
这是一个一元二次方程,可以解出它的两个根,不难发现均符合条件。进而地,设两个公比为
也满足给定递推公式,并且由给定的初始项,我们可以用待定系数法解出
由上,我们将求解“公比”的方程定义为特征方程,将求出的“公比”定义为特征根。
:::
我们求解特征方程
利用待定系数法代入初值,得到通项公式
其中
对于 m=3
如果
如果
及其上下翻转。我们注意到,对于
我们利用简单的错位相减知识,可以得到:
求解特征方程
同理得到通项公式:
其中
有了基本递推式后,我们需要快速求解
和式化简
对于 m=2
考虑组合数与下降幂的转化:
其中
:::success[关于第一类斯特林数及本公式推导,可跳过] 证明:
以下推导用到
考虑数学归纳法,当
假设当
我们观察
说了这么多,第一类斯特林数(这里是有编号的)到底是什么呢?其实你也不用关心,因为下面代码中允许你
我们把
我们把求和符号
注意到最后一项是一个等比数列求和,套用公式法:
- 当公比
q=x_1^jx_2^{i-j}=1 时,和为(r-l+1) \bmod p ; - 否则和为
\dfrac{q^l-q^{r+1}}{1-q} 。
对于 m=3
大致同理,但注意到
容易注意到最后还是一个等比数列,类似求解即可。
扩域
到这里我们就做完了……吗?
实际你会发现,我们的
我们类比复数,构建一个代数扩域
:::success[浅论“扩域”] 对于数学知识比较薄弱的同学,这里看不懂也没有关系,只需要知道我们把数的规模从整数拓展到了“整数与给定根式的加减乘除”的规模即可。
而对于数学知识较强的同学,我们注意到集合
我们称这样的集合为“数域”(仅考虑数的范围)。容易类比到复数域。实际上,有理数域、实数域、复数域都是常见的域。但是整数环缺少乘法逆元(比如
我们将所有运算符表示为
这下我们真的做完了,时间复杂度为
如果你有决心写这道题的话,代码应当是不难的。
:::success[code]
ll D;
struct field{
ll r,i;
field(ll r=0,ll i=0):r(r%mod),i(i%mod){
if(this->r<0)this->r+=mod;
if(this->i<0)this->i+=mod;
}
field operator+(const field&o)const{return field(r+o.r,i+o.i);}
field operator-(const field&o)const{return field(r-o.r,i-o.i);}
field operator*(const field&o)const{
return field(r*o.r+i*o.i%mod*D,r*o.i+i*o.r);
}
field operator*(ll val)const{return field(r*val,i*val);}
field invv()const{
ll low=(r*r%mod-i*i%mod*D%mod+mod)%mod;low=inv(low);
return field{r*low,(mod-i)*low};
}
field operator/(const field&o)const{return*this*o.invv();}
};
field qp(field a,ll b){
field ret(1,0);
for(;b;b>>=1,a=a*a)
if(b&1)ret=ret*a;
return ret;
}
field sumup(field x,ll l,ll r){
if(x.r==1&&x.i==0)return field((r-l+1)%mod,0);
field upp=qp(x,l)-qp(x,r+1),low=field(1,0)-x;
return upp/low;
}
D=(m==2)?5:3;
field x1,x2,c1,c2;
if(m==2){
ll i2=inv(2),i10=inv(10);
x1=field(i2,i2),x2=field(i2,mod-i2);
c1=field(5*i10,i10),c2=field(5*i10,mod-i10);
}else{
ll i6=inv(6);
x1=field(2,1),x2=field(2,mod-1);
c1=field(3*i6,i6),c2=field(3*i6,mod-i6);
}
while(T--){
ll l,r;int k;
cin>>l>>r>>k;
ll len=(r-l+1)%mod;
if(m==3)l=(l+1)/2,r/=2;
field tot(0,0);
if(l>r){cout<<"0\n";continue;}
for(int i=0;i<=k;i++){
field tmp(0,0);
for(int j=0;j<=i;j++){
field x=qp(x1,j)*qp(x2,i-j);
field Kp=qp(c1,j)*qp(c2,i-j)*c[i][j];
tmp=tmp+Kp*sumup(x,l,r);
}
tot=tot+tmp*s[k][i];
}
cout<<tot.r*inv(fac[k])%mod*inv(len)%mod<<'\n';
}
:::