• 【HDU 6310】Counting Permutations


    vjudge

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

    考虑对于一个给定序列如何求出(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)个分到左边,剩下分到右边,算一波最大值的贡献,分治到左右两边解决就好了,转移大概就是这个样子

    [dp_{i,j}=sum_{x=1}^isum_{k=x-1}inom{i-1}{x-1} imes dp_{x-1,k} imes dp_{i-x,j-k-min(x,i+1-x)} ]

    直接写看起来是(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;
    }
    
  • 相关阅读:
    TypeScript 函数
    单链表 C++
    测试用例概念 原则
    TypeScript 类
    TypeScript 接口
    Cellection
    面向对象
    反射
    B树
    无权无向图
  • 原文地址:https://www.cnblogs.com/asuldb/p/12344603.html
Copyright © 2020-2023  润新知