【HDU 6310】Counting Permutations

vjudge

为啥正解和暴力跑的差不多快呢;

考虑对于一个给定序列如何求出\(\displaystyle \sum_{i=1}^n\min(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}^i\min(k-l_k,r_k-k)=j\)的排列有多少个,我们枚举一下最大值的位置\(x\),之后从\(i-1\)里选\(x-1\)个分到左边,剩下分到右边,算一波最大值的贡献,分治到左右两边解决就好了,转移大概就是这个样子

\[dp_{i,j}=\sum_{x=1}^i\sum_{k=x-1}\binom{i-1}{x-1}\times dp_{x-1,k}\times dp_{i-x,j-k-\min(x,i+1-x)} \]

直接写看起来是\(O(n^6)\)的,但我们理性分析一下发现可能没有那么大。

\(F_i\)表示长度为\(i\)的排列的\(\displaystyle \sum_{k=1}^i\min(k-l_k,r_k-k)\)的最大值,\(F_i\)也可以用按照最大值分治的dp求出来,我们发现\(F_n\)远没有我们想象的\(n^2\)级别;理性分析一下,对\(F_i\)的转移树考虑,发现\(\min(x,i+1-x)\)实际上是左右子树中的较小值,于是我们大致可以认为这个复杂度和启发式合并类似,大概是\(n\log n\)级别,实际上还带一个非常小的常数,当\(n=200\)时,\(F_n\)只有\(736\)。于是上面那个东西直接做的复杂度大概是\(O(n^4\log^2 n)\),暴力卡卡常就已经能过了;

不难注意到\(dp_{i}\)是一个\(F_i\)次的多项式,证明的话归纳一波就可以;设\(G_i(x)\)表示表示\(dp_i\)的生成函数,于是有\(G_i(x)=\displaystyle \sum_{j=1}^i\binom{i-1}{j-1}G_{j-1}(x)G_{i-j}(x)x^{\min(j,i+1-j)}\),看起来似乎可以大力fft,但是\(O(n^3\log^2n)\)加上巨大常数跑得还没暴力快;fft慢在我们每次都需要DFT过去又IDFT回来,不妨直接将多项式搞成点值的形式,这样中间过程就不需要进行DFT和IDFT,直接点值相乘即可,所以对于每一个\(dp_i\)维护\(0\)\(F_n\)的点值,最后回答询问的时候直接拉格朗日插值回去就能得到系数了;

复杂度是\(O(n^3\log n+Tn^2\log^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\n",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;
}
posted @ 2020-02-22 11:14  asuldb  阅读(320)  评论(0编辑  收藏  举报