为啥正解和暴力跑的差不多快呢;
考虑对于一个给定序列如何求出(displaystyle sum_{i=1}^nmin(i-l_i,r_i-i)),一个简单的想法就是按照最大值分治,我们找到序列中最大值的位置(x),那么(x)的贡献就是(min(x,n+1-x)),之后再对([1,x-1])和([x+1,n])分治即可;
不妨按照这个思路进行dp,设(dp_{i,j})表示长度为(i)且(displaystyle sum_{k=1}^imin(k-l_k,r_k-k)=j)的排列有多少个,我们枚举一下最大值的位置(x),之后从(i-1)里选(x-1)个分到左边,剩下分到右边,算一波最大值的贡献,分治到左右两边解决就好了,转移大概就是这个样子
直接写看起来是(O(n^6))的,但我们理性分析一下发现可能没有那么大。
设(F_i)表示长度为(i)的排列的(displaystyle sum_{k=1}^imin(k-l_k,r_k-k))的最大值,(F_i)也可以用按照最大值分治的dp求出来,我们发现(F_n)远没有我们想象的(n^2)级别;理性分析一下,对(F_i)的转移树考虑,发现(min(x,i+1-x))实际上是左右子树中的较小值,于是我们大致可以认为这个复杂度和启发式合并类似,大概是(nlog n)级别,实际上还带一个非常小的常数,当(n=200)时,(F_n)只有(736)。于是上面那个东西直接做的复杂度大概是(O(n^4log^2 n)),暴力卡卡常就已经能过了;
不难注意到(dp_{i})是一个(F_i)次的多项式,证明的话归纳一波就可以;设(G_i(x))表示表示(dp_i)的生成函数,于是有(G_i(x)=displaystyle sum_{j=1}^iinom{i-1}{j-1}G_{j-1}(x)G_{i-j}(x)x^{min(j,i+1-j)}),看起来似乎可以大力fft,但是(O(n^3log^2n))加上巨大常数跑得还没暴力快;fft慢在我们每次都需要DFT过去又IDFT回来,不妨直接将多项式搞成点值的形式,这样中间过程就不需要进行DFT和IDFT,直接点值相乘即可,所以对于每一个(dp_i)维护(0)到(F_n)的点值,最后回答询问的时候直接拉格朗日插值回去就能得到系数了;
复杂度是(O(n^3log n+Tn^2log^2 n)),卡卡常才能跑得比暴力快;
代码
#include<bits/stdc++.h>
#define re register
#define LL long long
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
inline int read() {
char c=getchar();int x=0;while(c<'0'||c>'9') c=getchar();
while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
}
const int maxn=201;const int maxw=741;
int mod,lm[maxn],fac[maxw],ifac[maxw],N[11],M[11],T,inv[maxw];
int pw[maxw][maxw],dp[maxn][maxw],lim,cnt,g[maxw],h[maxw],ans;
inline int dqm(int x) {return x<0?x+mod:x;}
inline void upd(int &x,int y) {x+=y;if(x>=mod)x-=mod;}
inline void Lagrange(int n,int m) {
if(!n) {puts("1");return;}
memset(g,0,sizeof(g));g[0]=1;ans=0;
for(re int i=0;i<=lm[n];++i)
for(re int j=i;j>=0;--j) {
upd(g[j+1],g[j]);
g[j]=1ll*(mod-i)*g[j]%mod;
}
for(re int i=0;i<=lm[n];++i) {
if(!dp[n][i]) continue;
int v=1ll*ifac[i]*ifac[lm[n]-i]%mod*dp[n][i]%mod;
if((lm[n]-i)&1) v=(mod-v)%mod;
for(re int j=1;j<=lm[n];++j) h[j]=1ll*dqm(h[j-1]-g[j])*inv[i]%mod;
upd(ans,1ll*h[m]*v%mod);
}
printf("%d
",ans);
}
int main() {
scanf("%d",&mod);fac[0]=inv[1]=ifac[0]=pw[0][0]=1;int x,y;
while(scanf("%d%d",&x,&y)!=EOF) N[++T]=x,M[T]=y,lim=max(lim,N[T]);
for(re int i=1;i<=lim;++i)
for(re int j=1;j<=i;++j) lm[i]=max(lm[i],lm[j-1]+lm[i-j]+min(j,i+1-j));
cnt=lm[lim];
for(re int i=1;i<=cnt;i++) {
pw[i][0]=1;
for(re int j=1;j<=cnt;++j) pw[i][j]=1ll*pw[i][j-1]*i%mod;
}
for(re int i=1;i<=cnt;++i)fac[i]=1ll*fac[i-1]*i%mod;
for(re int i=2;i<=cnt;++i)inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
for(re int i=1;i<=cnt;++i)ifac[i]=1ll*ifac[i-1]*inv[i]%mod;
for(re int i=0;i<=cnt;++i)dp[0][i]=1;
for(re int i=1;i<=lim;i++) {
for(re int j=1;j<=((i+1)>>1);++j) {
int t=min(j,i+1-j),v=1ll*ifac[j-1]*ifac[i-j]%mod;
if(j-1!=i-j)upd(v,v);
for(re int k=0;k<=cnt;k++)
upd(dp[i][k],1ll*dp[j-1][k]*dp[i-j][k]%mod*pw[k][t]%mod*v%mod);
}
for(re int k=0;k<=cnt;++k) dp[i][k]=1ll*dp[i][k]*fac[i-1]%mod;
}
for(re int i=1;i<=T;++i) {
if(M[i]>lm[N[i]]||M[i]<N[i]) puts("0");
else Lagrange(N[i],M[i]);
}
return 0;
}