洛谷 P3352 [ZJOI2016]线段树 dp

探讨了在一个随机选择区间的序列上进行特定操作后,序列中各数值期望大小的计算方法。利用线段树解决该问题,并通过状态转移方程实现高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

小Yuuka遇到了一个题目:有一个序列a_1,a_2,…,a_n,q次操作,每次把一个区间内的数改成区间内的最大值,问最后每个数是多少。小Yuuka很快地就使用了线段树解决了这个问题。

于是充满智慧的小Yuuka想,如果操作是随机的,即在这q次操作中每次等概率随机地选择一个区间l,r,然后将这个区间内的数改成区间内最大值(注意这样的区间共有(n(n+1))/2(n(n+1))/2(n(n+1))/2个),最后每个数的期望大小是多少呢?小Yuuka非常热爱随机,所以她给出的输入序列也是随机的(随机方式见数据规模和约定)。对于每个数,输出它的期望乘((n(n+1))/2)q((n(n+1))/2)^q((n(n+1))/2)q再对109+710^9+7109+7取模的值。

输入输出格式

输入格式:
第一行包含2个正整数n,q,表示序列里数的个数和操作的个数。接下来1行,包含n个非负整数a1,a2…an。N&lt;=400,Q&lt;=400N&lt;=400,Q&lt;=400N<=400,Q<=400

输出格式:
输出共1行,包含n个整数,表示每个数的答案

输入输出样例

输入样例#1:
5 5
1 5 2 3 4
输出样例#1:
3152671 3796875 3692207 3623487 3515626

分析:
f[x][i][l][r]f[x][i][l][r]f[x][i][l][r]表示过了iii轮,lll~rrr的数都小于等于xxx的答案。
显然对于每个a[i]a[i]a[i],他能影响到的范围从左边第一个比他大的数的右边到右边第一个比他大的数的左边。

考虑转移,第一种是原区间转移,即f[x][i−1][l][r]f[x][i-1][l][r]f[x][i1][l][r]f[x][i][l][r]f[x][i][l][r]f[x][i][l][r],显然此时的更新区间要不是[l,r][l,r][l,r]的自己子集,要不是根本与[l,r][l,r][l,r]没有任何交集。
所以,f[x][i][l][r]=f[x][i−1][l][r]∗s[l][r]f[x][i][l][r]=f[x][i-1][l][r]*s[l][r]f[x][i][l][r]=f[x][i1][l][r]s[l][r]
其中,s[l][r]=(l−12)+(n−r2)+(r−l+12)s[l][r]=\binom{l-1}{2}+\binom{n-r}{2}+\binom{r-l+1}{2}s[l][r]=(2l1)+(2nr)+(2rl+1)
第二种转移是从f[x][i−1][l][k](k&gt;r)f[x][i-1][l][k](k&gt;r)f[x][i1][l][k](k>r)f[x][i−1][k][r](k&lt;l)f[x][i-1][k][r](k&lt;l)f[x][i1][k][r](k<l)转移,显然只要区间是子集或者根本没有交集即可。
然后可以使用前缀和优化求出,即可得出区间内任何一个数变成&lt;=x&lt;=x<=x的方案,求解即可。

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#define LL long long

const int N=407;
const LL mod=1e9+7;

using namespace std;

int n,m,l,r;
int a[N],b[N],s[N][N];
LL f[2][N][N],g[N][N],t;

int calc(int n)
{
    return n*(n+1)/2;
}

void solve(int l,int r,int d)
{
    memset(f,0,sizeof(f));
    int p=0;
    f[p][l][r]=1;
    for (int k=1;k<=m;k++)
    {
        p^=1;
        for (int i=l;i<=r;i++)
        {
        	t=0;
        	for (int j=r;j>=i;j--)
        	{
        		f[p][i][j]=t%mod;
        		t+=f[p^1][i][j]*(LL)(n-j);
        	}
        }
        for (int j=l;j<=r;j++)
        {
        	t=0;
        	for (int i=l;i<=j;i++)
        	{
        		f[p][i][j]=(f[p][i][j]+t+f[p^1][i][j]*s[i][j])%mod;
        		t+=f[p^1][i][j]*(LL)(i-1);
        	}
        }
    }
    for (int i=l;i<=r;i++)
    {
        t=0;
        for (int j=r;j>=i;j--)
        {
            t+=f[p][i][j],g[j][a[d]]=(g[j][a[d]]+t)%mod;
        }
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]),b[i]=a[i];
    sort(b+1,b+n+1);
    int sz=unique(b+1,b+n+1)-b-1;
    for (int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+sz+1,a[i])-b;
    for (int i=1;i<=n;i++)
    {
        for (int j=i;j<=n;j++) s[i][j]=calc(i-1)+calc(j-i+1)+calc(n-j);
    }	
    for (int i=1;i<=n;i++)
    {
        l=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++)
    {
        t=0;
        for (int j=1;j<=n;j++)
        {
            if (!g[i][j]) g[i][j]=g[i][j-1];
            t+=(g[i][j]-g[i][j-1])*b[j];
        }
        printf("%lld ",(t%mod+mod)%mod);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值