【牛客网】排列计数机

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}^{j\le A[i]-1}dp[j][o-1],o\in[2,i] \]

\[dp[j][o]=dp[j][o]\cdot 2,j\in[A[i]+1,n],o\in[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_1\cdot 1^{m}+k_2 \cdot 2^{m}+k_3 \cdot3^{m}+\cdots+k_n+{n^m}\)

再观察发现m最大才20,于是\(dp[i][j]\)表示最大值为i,其中记录的值为\(k_1\cdot 1^{j}+k_2\cdot 2^{j}+k_3 \cdot 3^{j}+\cdots+k_n\cdot 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,j\in[A[i]+1,n],o\in[1,i] \]

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

40分转移2:

\[dp[A[i]][o]=\sum _{j=1}^{j\le A[i]-1}dp[j][o-1],o\in[2,i] \]

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

那么,\(S_j=k_1\cdot 1^{j}+k_2\cdot 2^{j}+\cdots +k_n\cdot n^{j}\)\(k_i\)为构成权值为i的序列的方案数。

于是就有,

\[dp[A[i]][j]=k_1\cdot (1+1)^j+k_2\cdot(2+1)^j+\cdots+k_n\cdot(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]);
	}
}
posted @ 2019-11-06 14:15  TieT  阅读(256)  评论(0编辑  收藏  举报