bzoj 4574: [Zjoi2016]线段树 动态规划

题意

小Yuuka遇到了一个题目:有一个序列a_1,a_2,?,a_n,q次操作,每次把一个区间内的数改成区间内的最大值,问最后每个数是多少。小Yuuka很快地就使用了线段树解决了这个问题。于是充满智慧的小Yuuka想,如果操作是随机的,即在这q次操作中每次等概率随机地选择一个区间l,r,然后将这个区间内的数改成区间内最大值(注意这样的区间共有(n(n+1))/2个),最后每个数的期望大小是多少呢?小Yuuka非常热爱随机,所以她给出的输入序列也是随机的(随机方式见数据规模和约定)。对于每个数,输出它的期望乘((n(n+1))/2)^q再对10^9+7取模的值。
n<=400,Q<=400

分析

数据随机说明了序列中的数是两两不相同的。
sum[i,j] 表示把位置i操作q次后变成第j小的数有多少种方案。
问题在于如何求 sum[i,j]
先枚举一个位置p,设其能拓展到的区间为 [L,R] ,设 dp[k,i,j] 表示操作了k次后,极大区间 [i,j] 的所有值都不大于当前枚举的数的方案。极大区间表示如果 i>1 ,那么位置 i1 上的数一定大于当前枚举的数。 j 同理。
那么有

dp[k,i,j]=dp[k1,i,j]c[i,j]+u=Li1dp[k1,u,j](u1)+v=j+1Rdp[k1,i,v](nv)

其中 c[i,j]=cnt[i1]+cnt[nj]+cnt[ji+1] cnt[i] 表示长度为i的序列中有多少个不同的区间。
为什么是这样呢?
考虑如何证明第二项。
因为 [i,j] 是极大区间,假设之前 [u,j] 是极大区间,那么这一次操作就必须要把 [u,i1] 这一段变为大于当前枚举的数。而因为 [u,j] 是极大区间,意味着位置 u1 上的数必然大于当前枚举的数,所以当我们固定了右端点为 i 时,左端点的取值就有u1种。
第三项证明同理。
为了方便求出 sum[i,j] ,先设一个 sum[i,j] 表示位置i在操作q次后不大于第j小的数的方案,然后再求出 sum[i,j] 即可。
ans[i]=j=1nsum[i,j]w[j] ,其中 w[j] 表示第j小的数。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define dp(k,i,j) dp[(k)%2][i][j]
using namespace std;

typedef long long LL;

const int N=405;
const int MOD=1000000007;

int n,m,rank[N],a[N],w[N];
LL dp[2][N][N],sum[N][N],cnt[N],c[N][N];

int read()
{
    int x=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

void solve(int x,int L,int R)
{
    for (int i=L;i<=R;i++)
        for (int j=i;j<=R;j++)
            dp(0,i,j)=dp(1,i,j)=0;
    dp(0,L,R)=1;
    for (int k=1;k<=m;k++)
    {
        for (int i=L;i<=R;i++)
        {
            LL t=0;
            for (int j=R;j>=i;j--)
            {
                dp(k,i,j)=t;
                t+=(LL)dp(k-1,i,j)*(n-j);
            }
        }
        for (int j=L;j<=R;j++)
        {
            LL t=0;
            for (int i=L;i<=j;i++)
            {
                (dp(k,i,j)+=t)%=MOD;;
                t+=(LL)dp(k-1,i,j)*(i-1);
            }
        }
        for (int i=L;i<=R;i++)
            for (int j=i;j<=R;j++)
                (dp(k,i,j)+=dp(k-1,i,j)*c[i][j])%=MOD;
    }
    for (int i=L;i<=R;i++)
    {
        LL t=0;
        for (int j=R;j>=i;j--)
        {
            t+=dp(m,i,j);
            (sum[j][rank[x]]+=t)%=MOD;
        }
    }
}

int main()
{
    n=read();m=read();
    for (int i=1;i<=n;i++) a[i]=read(),w[i]=a[i],cnt[i]=cnt[i-1]+i;
    sort(w+1,w+n+1);
    for (int i=1;i<=n;i++) rank[i]=lower_bound(w+1,w+n+1,a[i])-w;
    for (int i=1;i<=n;i++)
        for (int j=i;j<=n;j++)
            c[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(i,L,R);
    }
    for (int i=1;i<=n;i++)
    {
        LL ans=0;
        for (int j=1;j<=n;j++)
        {
            if (!sum[i][j]) continue;
            for (int k=1;k<j;k++) sum[i][j]-=sum[i][k];
            while (sum[i][j]<0) sum[i][j]+=MOD;
            (ans+=(LL)sum[i][j]*w[j])%=MOD;
        }
        printf("%lld",(ans+MOD)%MOD);
        if (i<n) putchar(' ');
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值