BZOJ4574: [Zjoi2016]线段树【DP】

25 篇文章 0 订阅

4574: [Zjoi2016]线段树

这个DP有点玄学, F [ w ] [ i ] [ L ] [ R ] F[w][i][L][R] F[w][i][L][R]表示第 i i i m i n ( a [ i ] , i ∈ [ L , R ] ) ≤ w &lt; m i n ( a [ L − 1 ] , a [ R + 1 ] ) min(a[i],i\in[L,R])\le w &lt;min(a[L-1],a[R+1]) min(a[i],i[L,R])w<min(a[L1],a[R+1])

考虑转移

1. F [ w ] [ i ] [ j ] [ t ( t &lt; k ) ] + = F [ w ] [ i − 1 ] [ j ] [ k ] ∗ ( n − j ) F[w][i][j][t(t&lt;k)]+=F[w][i-1][j][k]*(n-j) F[w][i][j][t(t<k)]+=F[w][i1][j][k](nj),表示上一次的区间为 [ j , k ] [j,k] [j,k]这一次选择了 [ t + 1 , p ( p &gt; R ) ] [t+1,p(p&gt;R)] [t+1,p(p>R)] p p p可以在 [ n − j + 1 , n ] [n-j+1,n] [nj+1,n]之间随便选,这样选择包括了 R + 1 R+1 R+1所以不满足定义了,所以区间需要缩小。

2. F [ w ] [ i ] [ t ( t &gt; j ) ] [ k ] + = F [ w ] [ i − 1 ] [ j ] [ k ] ∗ ( i − 1 ) F[w][i][t(t&gt;j)][k]+=F[w][i-1][j][k]*(i-1) F[w][i][t(t>j)][k]+=F[w][i1][j][k](i1),同上。

3. F [ w ] [ i ] [ j ] [ k ] + = F [ w ] [ i ] [ j ] [ k ] ∗ ( i − 1 ) ∗ i 2 ∗ ( n − j ) ∗ ( n − j + 1 ) 2 ∗ ( j − i + 1 ) ∗ ( j − i + 2 ) 2 F[w][i][j][k]+=F[w][i][j][k]*\frac{(i-1)*i}{2}*\frac{(n-j)*(n-j+1)}{2}*\frac{(j-i+1)*(j-i+2)}{2} F[w][i][j][k]+=F[w][i][j][k]2(i1)i2(nj)(nj+1)2(ji+1)(ji+2)

也就是我们现在要取一个区间,使得要么被包含要么没有交集。

我们一开始枚举w,这样DP就可以少一维了,然后i这维滚一下。

然后前缀和优化DP就可以了。时间复杂度O(n^4),虽然跑不满,但是还是要卡一下常才行。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MOD=1e9+7;
typedef long long LL;
int n,q,m,Rank[405],b[405],a[405],cnt[405];
LL f[2][405][405],Ans[405],A[405][405],Sum[405][405];
void MO(LL &x){x-=(x>=MOD?MOD:0);}
void Solve(int L,int R,int K){
	memset(f,0,sizeof(f));f[0][L][R]=1;
	int now=1;
	for(int t=1;t<=q;t++,now^=1){
		for(int i=L;i<=R;i++){
			LL rt=0;
			for(int j=R;j>=i;j--) f[now][i][j]=rt,rt+=f[now^1][i][j]*(n-j);
		}
		for(int j=L;j<=R;j++){
			LL rt=0;
			for(int i=L;i<=j;i++) f[now][i][j]+=rt,rt+=f[now^1][i][j]*(i-1);
		}
		for(int i=L;i<=R;i++)
		for(int j=i;j<=R;j++) f[now][i][j]=(f[now][i][j]+f[now^1][i][j]*A[i][j])%MOD;
	}
	for(int i=L;i<=R;i++){
		LL rt=0;
		for(int j=R;j>=i;j--) MO(rt+=f[now^1][i][j]),MO(Sum[j][Rank[K]]+=rt);
	}
}
int main(){
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),Rank[i]=b[i]=a[i];
	sort(b+1,b+1+n);m=unique(b+1,b+1+n)-b-1;
	for(int i=1;i<=n;i++) Rank[i]=lower_bound(b+1,b+1+m,Rank[i])-b;
	for(int i=1;i<=n;i++) cnt[i]=i*(i+1)/2;
	for(int i=1;i<=n;i++)
	for(int j=i;j<=n;j++) A[i][j]=cnt[i-1]+cnt[n-j]+cnt[j-i+1];
	for(int i=1;i<=n;i++){
		int L=i,R=i;
		while(L>1&&a[L-1]<=a[i]) L--;
		while(R<n&&a[R+1]<=a[i]) R++;
		Solve(L,R,i);
	}
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++){
		if(!Sum[i][j]) continue;
		for(int u=1;u<j;u++) Sum[i][j]=(Sum[i][j]-Sum[i][u]+MOD)%MOD;
		MO(Ans[i]+=Sum[i][j]*b[j]%MOD);
	}
	for(int i=1;i<=n;i++) printf("%lld ",Ans[i]);printf("\n");
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值