https://www.luogu.com.cn/problem/P3643
https://loj.ac/p/2567
还在卡常,写了两天,醍醐灌顶的好题!
前言:感谢 wxy 老师的耐心指导。若有 F(x)≡G(x)modp,我们要求出 F(R)modp,但是给定的点值为 (x,F(x)modp) 的形式,也就是给出的是 (x,G(x)),那我们插值还是正确的吗?考虑插值的公式 H(x)=∑iyi∏k≠ix−xkxi−xk,那么现在给定的并不是 yi,而是 yimodp,xk,给定的也是 xkmodp,我们还能求出正确的答案吗?注意到本质上求的是 H(x)modp,那么你对于各个部分先取模,显然最后得到的也是对的。所以本质上我们并不是在插 G(x),仍然可看作在插 F(x),只是插最后的答案要 modp 而已。因此,我们仍然需要插 deg(F(x))+1 个点。
问题难点在于加速如下方程。
f(i,j)={∑jk=1f(i−1,k),ai≤j≤bif(i−1,j),otherwise
这里别当成函数,当成 f 数组。
我把第一个方程的操作称为类前缀和。
初始化为
f0(0)=1
f0(k)=0,k≠0
我认为难点在于如何理解它是个 fi 是个函数,且函数表达式为一个多项式,当然,有可能是个分段函数。
可通过数归来分析。
-
初始,可看成 f0(x)={1,x=00,otherwise,显然这是个分段函数,且每个函数表达式都是一个次数界为 1 的多项式。
-
转移,考虑到 i 的时候,fi 可能已经被分成了若干段,各段均为一个表达式是多项式的函数,
如图,那么,考虑 ai+1,bi+1 搞下去,
会分裂常数个函数,也就是会新增常数个段,那么绿色这种完整覆盖的,显然其函数只是相当于做了一个类前缀和操作,但其定义域是没发生变化的,但其次数会 +1,下面会具体分析这个操作,对于紫色的,没被覆盖到,显然啥都没变化。对于红色,蓝色的,会其被棕色覆盖到的部分,会进行一次类前缀和的操作,因此其函数会不同于原先。
-
对一个多项式函数施类前缀和操作,只会让其次数 +1,并不会改变其是一个多项式的事实。

考虑对蓝色块进行前缀和,那么显然其 F(x)=C+∑k≤xf(x),其中 C 为红色段与绿色段的和,然后这个东西升次了,可以用有限微积分来分析,当然积分直觉性地理解也可以,若有 G(x)=∫F(x)dx,则 deg(G)=deg(F)+1,然后对一个多项式积分之后显然还是一个多项式。
-
类前缀和操作只会操作整块。我们可以离散化端点,这样区间内的每个点受到的操作都是一样的了。
那么,到最后,fn 一定是一个由 O(n) 个次数界为 n+1 的多项式组成的分段函数。
考虑咋做,对于离散化后的区间 [l,r],设其函数为 Fi(x),这里有下标是因为我们要对于每个 i,求出 f(i,j),因此其每个 i 的函数都不一样。那么为了转移,我们要求出 r∑k=lF(k),那么显然要求出来 f(i,k),0≤i≤n,l≤k≤l+n,即插 n+1 个点,然后通过拉格朗日插值求出来,但是不是需要多点求值!显然,你再维护一个 g(i,j)=∑l≤k≤jf(i,k),即可,又因为这个操作等价于类前缀和,所以 Gi(x) 为对 Fi(x) 求前缀和,注意,二者定义域都在 [l,r],插 n+2 个点就能求出来了。然后一些精细实现,本题难点并不在这。
一些实现注意点:
-
虽然插值共有 O(n2) 次,但每 O(n) 次插的点的 x 都是一样的,因此可以预处理 ∏,单次均摊 O(n)。
-
因为我们对于每个 x,预先处理了 ∏x−xk,因此 x−xi 必须存在逆元,即其不为 0,否则的话可以特判规避下,即若区间长度 <=n+1 直接暴力,否则直接插,因为 p 很大,因此我们这样是对的。
#include <bits/stdc++.h>
#define pb push_back
#define ADD(x,y) (((x)+(y)>=mod?(x)+((y)-mod):((x)+(y))))
using namespace std;
const int N=520,mod=(int)(1e9+7),M=(int)(5e6+5);
int f[N][N],g[N][N];
int sum[N],lsh[N*100],tot,a[N],b[N],k,n;
int fpow(int x,int y) {
int res=1; x%=mod;
while(y) {
if(y&1) res=1ll*res*x%mod;
y>>=1; x=1ll*x*x%mod;
} return res;
}
int X[N],Y[N],cnt,p[N],q[N],pre[N],r1;
int F(int x) {
int res=0;
for(int i=1;i<=cnt;i++) {
if(p[i]==-1) {
p[i]=q[i]=1;
p[i]=1ll*r1*fpow(x-X[i],mod-2)%mod;
if(i) q[i]=pre[i-1];
if(i<cnt) {
q[i]=1ll*q[i]*pre[cnt-i]%mod;
if((cnt-i)&1) q[i]=mod-q[i];
}
p[i]=(p[i]%mod+mod)%mod;
q[i]=fpow(q[i],mod-2);
q[i]=(q[i]%mod+mod)%mod;
p[i]=1ll*p[i]*q[i]%mod;
p[i]=(p[i]%mod+mod)%mod;
}
res=ADD(res,1ll*Y[i]*p[i]%mod);
}
return res;
}
signed main() {
cin.tie(0); ios::sync_with_stdio(false);
cin>>n;
pre[0]=1; for(int i=1;i<=n+10;i++) pre[i]=1ll*pre[i-1]*i%mod;
for(int i=1;i<=n;i++) {
cin>>a[i]>>b[i];
lsh[++tot]=a[i]; lsh[++tot]=b[i];
lsh[++tot]=max(0,a[i]-1);
lsh[++tot]=b[i]+1;
}
f[0][0]=1;
lsh[++tot]=0;
stable_sort(lsh+1,lsh+1+tot); tot=unique(lsh+1,lsh+1+tot)-lsh-1;
for(int i=1;i<tot;i++) {
int l=lsh[i],r=lsh[i+1]-1;
if(r<=l+n) {
for(int j=0;j<=n;j++) {
if(a[j]<=l&&r<=b[j]) {
if(j)
for(int k=l;k<=r;k+=4) {
f[j][k-l]=ADD(g[j-1][k-l],sum[j-1]);
if(k+1<=r) f[j][k-l+1]=ADD(g[j-1][k-l+1],sum[j-1]);
if(k+2<=r) f[j][k-l+2]=ADD(g[j-1][k-l+2],sum[j-1]);
if(k+3<=r) f[j][k-l+3]=ADD(g[j-1][k-l+3],sum[j-1]);
}
} else {
if(j)
for(int k=l;k<=r;k+=4) {
f[j][k-l]=f[j-1][k-l];
if(k+1<=r) f[j][k-l+1]=f[j-1][k-l+1];
if(k+2<=r) f[j][k-l+2]=f[j-1][k-l+2];
if(k+3<=r) f[j][k-l+3]=f[j-1][k-l+3];
}
}
for(int k=l;k<=r;k+=4) {
g[j][k-l]=f[j][k-l];
if(k>l) g[j][k-l]=ADD(g[j][k-l],g[j][k-1-l]);
if(k+1<=r) {
g[j][k-l+1]=f[j][k-l+1];
if(k+1>l) g[j][k-l+1]=ADD(g[j][k-l+1],g[j][k-1-l+1]);
}
if(k+2<=r) {
g[j][k-l+2]=f[j][k-l+2];
if(k+2>l) g[j][k-l+2]=ADD(g[j][k-l+2],g[j][k-1-l+2]);
}
if(k+3<=r) {
g[j][k-l+3]=f[j][k-l+3];
if(k+3>l) g[j][k-l+3]=ADD(g[j][k-l+3],g[j][k-1-l+3]);
}
}
}
for(int j=0;j<=n;j++) {
for(int k=l;k<=r;k+=4) {
sum[j]=ADD(sum[j],f[j][k-l]);
if(k+1<=r) sum[j]=ADD(sum[j],f[j][k-l+1]);
if(k+2<=r) sum[j]=ADD(sum[j],f[j][k-l+2]);
if(k+3<=r) sum[j]=ADD(sum[j],f[j][k-l+3]);
}
}
for(int j=0;j<=n;j++) {
memset(f[j],0,sizeof(int)*(r-l+1));
memset(g[j],0,sizeof(int)*(r-l+1));
}
} else {
int R=l+n;
for(int j=0;j<=n;j++) {
if(a[j]<=l&&R<=b[j]) {
if(j)
for(int k=l;k<=R;k+=4) {
f[j][k-l]=ADD(g[j-1][k-l],sum[j-1]);
if(k+1<=R) f[j][k-l+1]=ADD(g[j-1][k-l+1],sum[j-1]);
if(k+2<=R) f[j][k-l+2]=ADD(g[j-1][k-l+2],sum[j-1]);
if(k+3<=R) f[j][k-l+3]=ADD(g[j-1][k-l+3],sum[j-1]);
}
} else {
if(j)
for(int k=l;k<=R;k+=4) {
f[j][k-l]=f[j-1][k-l];
if(k+1<=R) f[j][k-l+1]=f[j-1][k-l+1];
if(k+2<=R) f[j][k-l+2]=f[j-1][k-l+2];
if(k+3<=R) f[j][k-l+3]=f[j-1][k-l+3];
}
}
for(int k=l;k<=R;k+=4) {
g[j][k-l]=f[j][k-l];
if(k>l) g[j][k-l]=ADD(g[j][k-l],g[j][k-1-l]);
if(k+1<=R) {
g[j][k-l+1]=f[j][k-l+1];
if(k+1>l) g[j][k-l+1]=ADD(g[j][k-l+1],g[j][k-1-l+1]);
}
if(k+2<=R) {
g[j][k-l+2]=f[j][k-l+2];
if(k+2>l) g[j][k-l+2]=ADD(g[j][k-l+2],g[j][k-1-l+2]);
}
if(k+3<=R) {
g[j][k-l+3]=f[j][k-l+3];
if(k+3>l) g[j][k-l+3]=ADD(g[j][k-l+3],g[j][k-1-l+3]);
}
}
}
for(int j=1;j<=R-l+1;j++) p[j]=q[j]=-1;
cnt=R-l+1; r1=1;
for(int j=1;j<=cnt;j++) X[j]=j+l-1,r1=1ll*r1*(r-X[j])%mod;
for(int j=0;j<=n;j++) {
for(int k=l;k<=R;k++) Y[k-l+1]=g[j][k-l];
sum[j]=ADD(sum[j],F(r));
}
for(int j=0;j<=n;j++) {
memset(f[j],0,sizeof(int)*(R-l+1));
memset(g[j],0,sizeof(int)*(R-l+1));
}
}
}
int ans=((sum[n]-1)%mod+mod)%mod;
cout<<ans;
return 0;
}
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】