【BZOJ】4574: [Zjoi2016]线段树-DP

传送门:bzoj4574

题解

转自 lychcys l y c h c y s
题意就是求每个数在所有方案中的最终值的和。显然一个数经过若干次变化一定会变成另外一个数,那么离散化后,令 g[i][j] g [ i ] [ j ] 表示i这个数最终变成从小到大第j个的方案数。一个直观的思路是,我们枚举j,那么显然 g[i][j]>0 g [ i ] [ j ] > 0 的i的范围是 (l,r) ( l , r ) ,其中a[l]和a[r]是第j大的数两侧分别第一个大于这个从小到大第j个数的数(由于是随机因此可以假定没有两个数相同)。此时,
如果令 f[k][x][y] f [ k ] [ x ] [ y ] 表示经过k轮后,恰好是 [x,y] [ x , y ] 这个范围内的数都变成了从小到大第j个数的方案数。但是这样会存在问题,就是如果某一轮的操作跨过了l或r,就会造成 [l,r] [ l , r ] 中某一些数 > > 从小到大第j个数,这样再转移就会出错。
那么令f[k][x][y]表示经过k轮后,恰好 [x,y] [ x , y ] 这个范围内的数都 从小到大第j数的方案数,这样就可以转移了。显然f[k][x][y]必然由 f[k][u(u<x)][y] f [ k ] [ u ( u < x ) ] [ y ] f[k][x][v(v>y)] f [ k ] [ x ] [ v ( v > y ) ] ,然后操作任意 [t(t<l),u] [ t ( t < l ) , u ] [v,t(t>r)] [ v , t ( t > r ) ] 得到;或者直接由 f[k][x][y] f [ k ] [ x ] [ y ] ,然后任意一个操作 [u,v] [ u , v ] ,其中 [u,v][x,y]=0 [ u , v ] ∩ [ x , y ] = 0 得到。
然后重新令 g[i][j] g [ i ] [ j ] 表示第i个数最终 从小到大第j个数的方案数,然后减一减即可。
后来有一个卡常的技巧是每一轮都用long long暴力加起来,这一轮结束的时候再取模。这样瞬间应该可以快很多。


代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define mod 1000000007
#define ll long long
#define N 405
#define calc(x) ((x)*((x)+1)>>1)
using namespace std;
int n,m,f[2][N][N],g[N][N],s[N][N],a[N],b[N],c[N],rnk[N];
bool cmp(const int &x,const int &y){ return a[x]<a[y]; }
int read(){
    int x=0; char ch=getchar();
    while (ch<'0' || ch>'9') ch=getchar();
    while (ch>='0' && ch<='9'){ x=x*10+ch-'0'; ch=getchar(); }
    return x;
}
void solve(int l,int r,int p){
    int i,j,k,last,now=0,tmp;
    for (i=l; i<=r; i++) for (j=i; j<=r; j++) f[0][i][j]=0;
    f[0][l][r]=1;
    for (k=1; k<=m; k++){
        last=now; now^=1;
        for (i=l; i<=r; i++){
            tmp=0;
            for (j=r; j>=i; j--){
                f[now][i][j]=tmp; tmp=(tmp+(ll)f[last][i][j]*(n-j))%mod;
            }
        }
        for (i=l; i<=r; i++) c[i]=0;
        for (i=l; i<=r; i++)
            for (j=i; j<=r; j++){
                f[now][i][j]=(f[now][i][j]+c[j])%mod; c[j]=(c[j]+(ll)f[last][i][j]*(i-1))%mod;
            }
        for (i=l; i<=r; i++)
            for (j=i; j<=r; j++) f[now][i][j]=(f[now][i][j]+(ll)f[last][i][j]*s[i][j])%mod;
    }
    for (i=l; i<=r; i++){
        tmp=0;
        for (j=r; j>=i; j--){
            tmp=(tmp+f[now][i][j])%mod; g[j][rnk[p]]=(g[j][rnk[p]]+tmp)%mod;
        }
    }
}
int main(){
    n=read(); m=read(); int i,j,l,r,tmp,ans;
    for (i=1; i<=n; i++){ a[i]=read(); b[i]=i; }
    sort(b+1,b+n+1,cmp);
    for (i=1; i<=n; i++) rnk[b[i]]=i;
    for (i=1; i<=n; i++)
        for (j=i; j<=n; j++) s[i][j]=calc(i-1)+calc(n-j)+calc(j-i+1);
    for (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 (i=1; i<=n; i++){
        tmp=ans=0;
        for (j=1; j<=n; j++) if (g[i][j]){
            g[i][j]-=tmp; if (g[i][j]<0) g[i][j]+=mod;
            ans=(ans+(ll)g[i][j]*a[b[j]])%mod; tmp=(tmp+g[i][j])%mod;
        }
        printf("%d%c",ans,(i<n)?' ':'\n');
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值