• 【牛客网】排列计数机


    dp+线段树+数学

    10pts:n<=20

    无脑二进制暴力枚举就可以了。

    20pts:n<=100

    大力dp,设(dp[i][j][k])表示考虑了前i个数,并且第i个数必须选,前i个数所构成子序列的最大值为j,子序列权值为k的方案数。

    大力转移即可。

    复杂度:(O(n^4))

    代码:

    namespace p20 {
    	int dp[110][110][110],ans;
    	void work() {
    		for(register int i=1; i<=n; ++i) {
    			dp[i][A[i]][1]=1;
    			for(register int j=1; j<=A[i]-1; ++j) {
    				for(register int k=1; k<=i-1; ++k) {
    					for(register int o=2; o<=i; ++o) {
    						dp[i][A[i]][o]=(dp[i][A[i]][o]+dp[k][j][o-1])%MOD;
    					}
    				}
    			}
    			for(register int j=A[i]+1; j<=n; ++j) {
    				for(register int k=1; k<=i-1; ++k) {
    					for(register int o=1; o<=i; ++o) {
    						dp[i][j][o]=(dp[i][j][o]+dp[k][j][o])%MOD;
    					}
    				}
    			}
    		}
    		for(register int i=1; i<=n; ++i) {
    			for(register int j=1; j<=n; ++j) {
    				for(register int k=1; k<=i; ++k) {
    					ans=(0LL+ans+1LL*dp[i][j][k]*Quick_Pow(k,m)%MOD)%MOD;
    				}
    			}
    		}
    		cout<<ans;
    	}
    }
    

    40pts:n<=1000

    考虑20pts的dp,你会发现第一维是完全没必要的。

    (dp[i][j])表示最大值为i的序列,权值为j的方案数。

    转移方程:(对于每一个i)

    [dp[A[i]][1]=1 ]

    [dp[A[i]][o]=sum _{j=1}^{jle A[i]-1}dp[j][o-1],oin[2,i] ]

    [dp[j][o]=dp[j][o]cdot 2,jin[A[i]+1,n],oin[1,i] ]

    复杂度:(O(n^3))(完全跑不满,再加上3s时限,随便过)

    代码:

    namespace p40 {
    	int dp[1010][1010],ans;
    	void work() {
    		for(register int i=1; i<=n; ++i) {
    			dp[A[i]][1]=1;
    			for(register int j=1; j<=A[i]-1; ++j) {
    				for(register int o=2; o<=i; ++o) {
    					dp[A[i]][o]=(dp[A[i]][o]+dp[j][o-1])%MOD;
    				}
    			}
    			for(register int j=A[i]+1; j<=n; ++j) {
    				for(register int o=1; o<=i; ++o) {
    					dp[j][o]=(dp[j][o]+dp[j][o])%MOD;
    				}
    			}
    
    		}
    		for(register int j=1; j<=n; ++j) {
    			for(register int k=1; k<=n; ++k) {
    				ans=(0LL+ans+1LL*dp[j][k]*Quick_Pow(k,m)%MOD)%MOD;
    			}
    		}
    		cout<<ans;
    	}
    }
    

    20pts:m=1

    当m=1时,显然就变成了求所有子序列的权值和。

    那么,我们算一个数(A[i])在多少个子序列中有贡献。

    考虑到若一个数(A[i])有贡献,那么在[1,i-1]这一段中所选的数必须小于(A[i]),而在[i+1,n]这一段中不管怎么选,都没有影响。

    设在[1,i-1]中有(x)个数小于(A[i]),那么(A[i])的贡献就是(2^{n-i+x})

    树状数组和快速幂搞一下就可以了。

    代码:

    namespace m1 {
    	int BIT[MAXN],ans;
    	void Add(int i,int x) {
    		while(i<=n)BIT[i]++,i+=i&-i;
    	}
    	int Query(int i) {
    		int res=0;
    		while(i>=1)res+=BIT[i],i-=i&-i;
    		return res;
    	}
    	void work() {
    		for(int i=1; i<=n; i++) {
    			int x=Query(A[i]);
    			ans=(ans+1LL*Quick_Pow(2,x+n-i))%MOD;
    			Add(A[i],1);
    		}
    		cout<<ans;
    	}
    }
    

    100pts:一般情况

    由于权值可能为n,而n最大是1e5,显然我们不能存权值。

    考虑我们最终求得答案肯定是(k_1cdot 1^{m}+k_2 cdot 2^{m}+k_3 cdot3^{m}+cdots+k_n+{n^m})

    再观察发现m最大才20,于是(dp[i][j])表示最大值为i,其中记录的值为(k_1cdot 1^{j}+k_2cdot 2^{j}+k_3 cdot 3^{j}+cdots+k_ncdot n^{j})

    同时,我们还需要一棵权值线段树来维护。

    struct node {
    	int L,R,val[22],lazy;
    }
    void Up(int p) {
    	for(register int i=0; i<=m; ++i)tree[p].val[i]=(0LL+tree[p<<1].val[i]+tree[p<<1|1].val[i])%MOD;
    }
    

    再看一下转移。

    40分转移1:

    [dp[j][o]=dp[j][o]cdot 2,jin[A[i]+1,n],oin[1,i] ]

    对于这个转移,我们直接把([A[i]+1,n])这个区间的值乘以2。

    40分转移2:

    [dp[A[i]][o]=sum _{j=1}^{jle A[i]-1}dp[j][o-1],oin[2,i] ]

    对于每个(jin[0,m])我们先求出([1,A[i]-1])的val和,记为(S_j)

    那么,(S_j=k_1cdot 1^{j}+k_2cdot 2^{j}+cdots +k_ncdot n^{j})(k_i)为构成权值为i的序列的方案数。

    于是就有,

    [dp[A[i]][j]=k_1cdot (1+1)^j+k_2cdot(2+1)^j+cdots+k_ncdot(n+1)^j ]

    对于((x+1)^j)我们可以二次项展开。

    对于((x+1)^j),有:

    [(x+1)^j=C_{j}^{j}x^j+C_{j}^{j-1}x^{j-1}+C_j^{j-2}x^{k-2}+cdots+C_{j}^{0}x^0 ]

    那么,

    [dp[A[i]][j]=C^j_{j}cdot S_j+C_{j}^{j-1}cdot S_{j-1}+cdots+C_j^{0}cdot S_0 ]

    不懂得话自己手推一下吧

    然后再塞回线段树中就好了。

    复杂度:(O(nm^2+nmlog(n)))(时限虽然3s,但还是很卡常)

    代码:

    namespace p100 {
    	int dp[MAXN][30],C[22][22];
    	void init() {
    		C[0][0]=1;
    		for(int i=1; i<=20; i++) {
    			C[i][0]=1;
    			for(int j=1; j<=i; j++)C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
    		}
    	}
    	struct node {
    		int L,R,val[22],lazy;
    	} tree[MAXN<<2];
    	node res;
    	void Up(int p) {
    		for(register int i=0; i<=m; ++i)tree[p].val[i]=(0LL+tree[p<<1].val[i]+tree[p<<1|1].val[i])%MOD;
    	}
    	void build(int L,int R,int p) {
    		tree[p].L=L,tree[p].R=R;
    		tree[p].lazy=1;
    		if(L==R)return;
    		int mid=(L+R)>>1;
    		build(L,mid,p<<1);
    		build(mid+1,R,p<<1|1);
    	}
    	void Down(int p) {
    		if(tree[p].lazy==1)return;
    		for(int i=0; i<=m; i++) {
    			tree[p<<1].val[i]=1LL*tree[p<<1].val[i]*tree[p].lazy%MOD;
    			tree[p<<1|1].val[i]=1LL*tree[p<<1|1].val[i]*tree[p].lazy%MOD;
    		}
    		tree[p<<1].lazy=1LL*tree[p<<1].lazy*tree[p].lazy%MOD;
    		tree[p<<1|1].lazy=1LL*tree[p<<1|1].lazy*tree[p].lazy%MOD;
    		tree[p].lazy=1;
    	}
    	void update_times2(int L,int R,int p) {
    		if(tree[p].L==L&&tree[p].R==R) {
    			for(int i=0;i<=m;i++)tree[p].val[i]=(tree[p].val[i]+tree[p].val[i])%MOD;
    			tree[p].lazy=(tree[p].lazy+tree[p].lazy)%MOD;
    			return;
    		}
    		Down(p);
    		int mid=(tree[p].L+tree[p].R)>>1;
    		if(R<=mid)update_times2(L,R,p<<1);
    		else if(L>=mid+1)update_times2(L,R,p<<1|1);
    		else update_times2(L,mid,p<<1),update_times2(mid+1,R,p<<1|1);
    		Up(p);
    	}
    	node Query(int L,int R,int p) {
    		if(tree[p].L==L&&tree[p].R==R)return tree[p];
    		Down(p);
    		int mid=(tree[p].L+tree[p].R)>>1;
    		if(R<=mid)return Query(L,R,p<<1);
    		else if(L>=mid+1)return Query(L,R,p<<1|1);
    		else {
    			node Ans,S1=Query(L,mid,p<<1),S2=Query(mid+1,R,p<<1|1);
    			for(int i=0;i<=m;i++)Ans.val[i]=(S1.val[i]+S2.val[i])%MOD;
    			return Ans;
    		}
    	}
    	void Set(int pos,int p) {
    		if(tree[p].L==tree[p].R) {
    			for(int i=0;i<=m;i++)tree[p].val[i]=dp[pos][i];
    			return;
    		}
    		Down(p);
    		int mid=(tree[p].L+tree[p].R)>>1;
    		if(pos<=mid)Set(pos,p<<1);
    		else Set(pos,p<<1|1);
    		Up(p);
    	}
    	void work() {
    		init();
    		build(1,n,1);
    		for(register int i=1; i<=n; ++i) {
    			memset(res.val,0,sizeof(res.val));
    			if(A[i]+1<=n)update_times2(A[i]+1,n,1);
    			if(A[i]-1>=1)res=Query(1,A[i]-1,1);
    			for(register int j=0; j<=m; ++j) {
    				dp[A[i]][j]=1;
    				for(register int k=j; k>=0; --k) {
    					dp[A[i]][j]=(dp[A[i]][j]+1LL*C[j][k]*res.val[k]%MOD)%MOD;
    				}
    			}
    			Set(A[i],1);
    		}
    		printf("%d",Query(1,n,1).val[m]);
    	}
    }
    
  • 相关阅读:
    leetcode32 Longest Valid Parentheses 最长有效括号序列
    js的Prototype属性
    一道区间dp和一道字符串
    Cookie和Session
    JS选择器querySelector和~All,三个原生选择器
    module.exports和exports
    Windows7环境下MongoDB安装和配置
    转载:Ajax基础详解&&http填坑2
    Node后台刷评论
    Node 简单爬虫
  • 原文地址:https://www.cnblogs.com/SillyTieT/p/11804871.html
Copyright © 2020-2023  润新知