【BZOJ4574】[ZJOI2016] 线段树(动态规划)

点此看题面

  • 给定一个长度为\(n\)的数组\(a_i\)
  • 共会执行\(q\)次操作,每次随机选择一个区间,让区间中的所有数都成为这个区间的最大值。
  • 问数组每个位置最终元素值的期望。
  • \(n,q\le400\)

震惊,没想到居然能自己推出这道题来!

枚举最终值

考虑既然是求期望,我们不如枚举\(x\),去计算第\(p\)个位置最终值为\(x\)的方案数。

恰为\(x\)不好求,一个简单的容斥就是变成求最终值小于等于\(x\)的方案数\(G_{p,x}\),那么实际方案数就是\(G_{p,x}-G_{p,x-1}\)

此时,我们就可以把所有小于等于\(x\)的数视作\(0\),大于\(x\)的数视作\(1\)

一开始序列中可能会有若干\(1\),把序列分成了很多小段。而由于\(1\)是不可能消掉的,所以小段之间是相互独立的。

那么我们不妨设\(f_{i,j,k}\)表示\(i\)次操作之后,\([j,k]\)中的数都是\(0\),且\(f_{j-1}=f_{k+1}=1\)的方案数。

转移时总共会有三种可能的情况:

  • 当前修改区间不会影响到\([j,k]\)中的值。那么就是在\([1,j),[j,k],(k,n]\)中某一块内选择一个区间的方案数。
  • 一段前缀被修改为\(1\)。则枚举原本的左端点\(t\),有\(f_{i,j,k}=\sum_{t=1}^{j-1}f_{i,t,k}\times(t-1)\),显然可以前缀和优化转移。
  • 一段后缀被修改为\(1\)。则枚举原本的右端点\(t\),有\(f_{i,j,k}=\sum_{t=k+1}^nf_{i,j,t}\times(n-t)\),类似地可以后缀和优化转移。

于是我们已经有了一个\(O(n^4)\)的做法了。

最终贡献化数组初值

我们发现对于不同的\(x\)\(DP\)转移的过程是完全一样的。

\(s\)表示\(a\)排序后的结果,考虑最终答案的计算,就是\(\sum_{i=1}^ns_i(G_{p,s_i}-G_{p,s_{i-1}})\)

稍微变个形就是\(\sum_{i=1}^n(s_i-s_{i+1})G_{p,s_i}\)

考虑原先对于\(x\)\(DP\)的初始状态,本应是给满足\(a_{j\sim k}\)\(\le x\)\(a_{j-1}>x,a_{k+1}>x\)的区间\(DP\)初值\(f_{0,j,k}\)赋为\(1\)

因此现在我们只要先对于每个\(x\),给满足\(a_{j\sim k}\)\(\le x\)\(a_{j-1}>x,a_{k+1}>x\)的区间\(DP\)初值\(f_{0,j,k}\)加上\(s_i-s_{i+1}\),再一起\(DP\)一遍就好了。

代码:\(O(n^3)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 400
#define X 1000000007
#define S(x) ((x)*((x)+1)/2)
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n,m,a[N+5],s[N+5],f[2][N+5][N+5];
int main()
{
	RI i,j,k;for(scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%d",a+i),s[i]=a[i];
	for(a[0]=a[n+1]=1e9,sort(s+1,s+n+1),i=1;i<=n;++i) for(j=1;j<=n;++j) if(a[j-1]>s[i])
		for(k=j;k<=n&&a[k]<=s[i];++k) if(a[k+1]>s[i]) f[0][j][k]=(f[0][j][k]+s[i]-s[i+1]+X)%X;//初始化
	RI o,t;for(i=o=1;i<=m;++i,o^=1)//动态规划
	{
		for(j=1;j<=n;++j) for(k=j;k<=n;++k) f[o][j][k]=1LL*f[o^1][j][k]*(S(j-1)+S(n-k)+S(k-j+1))%X;//不变
		for(k=1;k<=n;++k) for(t=0,j=1;j<=k;++j) Inc(f[o][j][k],t),t=(1LL*f[o^1][j][k]*(j-1)+t)%X;//变左端点
		for(j=1;j<=n;++j) for(t=0,k=n;k>=j;--k) Inc(f[o][j][k],t),t=(1LL*f[o^1][j][k]*(n-k)+t)%X;//变右端点
	}
	for(i=1;i<=n;printf("%d%c",t," \n"[i==n]),++i)//对每个位置,统计所有包含该位置的区间的答案
		for(t=0,j=1;j<=i;++j) for(k=i;k<=n;++k) Inc(t,f[m&1][j][k]);return 0;
}
posted @ 2020-12-11 17:37  TheLostWeak  阅读(34)  评论(0编辑  收藏