bzoj4574 [Zjoi2016]线段树 DP

168 篇文章 0 订阅

这题简直鬼畜= =
题意:给出一个序列,m次操作,每次随机取一个区间,将这个区间里面的数变为这个区间内的最大数字。输出每个数字的期望值*((n(n+1))/2)^q.
对于这种期望题目,如果不能直接算期望,那肯定是把除数拿出来然后直接算。
这里我们要算出每个数能变成什么数的方案数,那么明显dp。
g[i][j] 表示第i个数变为排名第j的数的方案数。
那么我们枚举j,假设rank[j]=x,那么 g[i][j]>0 的时候肯定是在区间l,r内,满足 a[l]...a[r]<=x .
然后我们来膜一发lych的题解。。

如果令f[k][x][y]表示经过k轮后,恰好是[x,y]这个范围内的数都变成了从小到大第j个数的方案数。但是这样会存在问题,就是如果某一轮的操作跨过了l或r,就会造成[l,r]中某一些数>从小到大第j个数,这样再转移就会出错。

所以令f[k][x][y]表示经过k轮后,恰好是[x,y]范围内的数都变成了小于a[rank[j]]的方案数。
那么有三种转移:
1.f[k][x][y]由f[k][x][y+1…n]转移而来。

f[k][x][y]+=r=y+1nf[k][x][r](nr)

2.f[k][x][y]由f[k][1…x-1][y]转移而来
基本同上。
3.由[u,v]转移而来,[u,v]与[x,y]无交集
那么直接预处理这种就好。
s[i][j]=calc(i1)+calc(ji+1)+calc(nj)
代码挺短,但是巨慢= =
30s我居然30.4s没T,也是神奇= =

#include<cstdio>
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=5e2+5;
const int mo=1e9+7;
int n,m;
typedef long long ll;
int f[2][N][N],g[N][N],s[N][N];
int a[N],b[N],c[N],rank[N],l,r,tmp,ans;
bool cmp(int x,int y)
{
    return a[x]<a[y];
}
inline int calc(int x)
{
    return x*(x+1)/2;
}
inline void solve(int l,int r,int p)
{
    int last,x=0,tmp;
    fo(i,l,r)
    fo(j,i,r)f[0][i][j]=0;
    f[0][l][r]=1;
    fo(k,1,m)
    {
        last=x;
        x^=1;
        fo(i,l,r)
        {
            tmp=0;
            fd(j,r,i)
            {
                f[x][i][j]=tmp;
                tmp=(tmp+1ll*f[last][i][j]*(n-j))%mo;
            }
        }
        fo(i,l,r)c[i]=0;
        fo(i,l,r)
        {
            fo(j,i,r)
            {
                f[x][i][j]=(f[x][i][j]+c[j])%mo;
                c[j]=(c[j]+1ll*f[last][i][j]*(i-1))%mo;
            }
        }
        fo(i,l,r)
        fo(j,i,r)
        f[x][i][j]=(f[x][i][j]+1ll*f[last][i][j]*s[i][j])%mo;
    }
    fo(i,l,r)
    {
        tmp=0;
        fd(j,r,i)
        {
            tmp=(tmp+f[x][i][j])%mo;
            g[j][rank[p]]=(g[j][rank[p]]+tmp)%mo;
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    fo(i,1,n)
    {
        scanf("%d",&a[i]);
        b[i]=i;
    }
    sort(b+1,b+1+n,cmp);
    fo(i,1,n)rank[b[i]]=i;
    fo(i,1,n)
    {
        fo(j,1,n)
        s[i][j]=calc(i-1)+calc(n-j)+calc(j-i+1);
    }
    fo(i,1,n)
    {
        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);
    }
    fo(i,1,n)
    {
        tmp=ans=0;
        fo(j,1,n)if (g[i][j])
        {
            g[i][j]-=tmp;
            if (g[i][j]<0)g[i][j]+=mo;
            ans=(ans+1ll*g[i][j]*a[b[j]])%mo;
            tmp=(tmp+g[i][j])%mo;
        }
        printf("%d%c",ans,(i<n)?' ':'\n');
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值