Sequence(ST表)(洛谷P2048)

超级钢琴

知识储备

在做这道题前,我们先要了解一下ST表(一种离线求区间最值的方法)

ST表使用DP实现的,其查询复杂度为O(1).

那么我们怎么用DP实现呢??

首先,我们设立一个状态f[i][j],其中i代表起点(包括在内),而j则代表是2的几次方,那么这个状态的含义就是以i为起点,求i~i+2^j-1内的区间最大值

最开始,我们定义f[i][0]的值为其本身即a[i]

那么我们转移方程就是

f[i][j]=max(f[i][j-1],f[i+(1<<(j-1)][j-1]);  //一层一层的推过来...

然后我们就知道在一定区间内的最大值了!!

查询也是一个需要特别注意的点,我们要知道查询的x位置和y位置之间max,就要知道y-x+1是y的几次方,其中这个要向下取整,防止越界,但是这样我们就会有一部分扫不到,由于我们向下取整的这个区间是大于等于这个区间的一半的,所以我们可以反过来,从y-(1<<k)到y,这样可以保证不会漏掉数据,并且重复的话也不会对答案产生影响

但是注意!!算k的式子要小心啊

别取错了,一个小BUG就会导致你WA掉...

#include<bits/stdc++.h>
#define ll long long
#define FOR(i,a,b) for(register int i=a;i<=b;i++)
#define ROF(i,a,b) for(register int i=a;i>=b;i--)
using namespace std;
const int N=500000+10;
int n,m;
int a[N];
int f[N][30];
int scan()
{
    int as=0,f=1;
    char c=getchar();
    while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
    while(c>='0'&&c<='9'){as=(as<<3)+(as<<1)+c-'0';c=getchar();}
    return as*f;
}
void RMQ(int n)
{
    FOR(i,1,20)
    {
        FOR(j,1,n)
        {
            if(j+(1<<i)-1<=n)
            f[j][i]=max(f[j][i-1],f[j+(1<<(i-1))][i-1]);
        }
    }
}
int main()
{
    n=scan();
    m=scan();
    FOR(i,1,n)
        a[i]=scan(),f[i][0]=a[i];
    RMQ(n);
    FOR(i,1,m)
    {
        int x,y;
        x=scan();
        y=scan();
        int k=(int)(log((double)(y-x+1))/log(2.0));
        printf("%d
",max(f[x][k],f[y-(1<<k)+1][k]));
    }
    return 0;
}
代码戳这里

接下来我们要讲正事了!!

我们的目光转到钢琴这一题上

思路

首先,我们前缀和储存值,然后在用KMQ,将一些区间内的max求出来

然后我们根据题目要求,由于题目要我们求最大权值,那么我们就求每个可能区间里面的max,将其储存在堆里面.

但是最开始的时候,我们只求出了以某一点为起点的最大值,但是我们最后要求的1~k个最大值的相加

所以我们在每取出一个max是,再去寻找以该点为起点的max,只不过此时的max不是当前的最有解,而是次优解,但是也可能成为新的最有解,

然后题目打这里就差不多啦

#include<bits/stdc++.h>
#define ll long long
#define gc getchar()
#define FOR(i,a,b) for(register int i=a;i<=b;i++)
#define ROF(i,a,b) for(register int i=a;i>=b;i--)
using namespace std;
const int N=500000+10;
int n,m,L,R;
ll sum[N];
int id[N][30],f[N][30];
ll ans=0;
int scan()
{
    int as=0,f=1;char c=gc;
    while(c>'9'||c<'0'){if(c=='-') f=-1;c=gc;}
    while(c>='0'&&c<='9'){as=(as<<3)+(as<<1)+c-'0';c=gc;}
    return as*f;
}
//learn new
struct s1
{
    int u,l,r,pos;
    bool operator <(s1 y) const
    {
        return sum[pos]-sum[u-1]<sum[y.pos]-sum[y.u-1];//大的放队尾
    }
}tmp;
priority_queue<s1>q;//队列
void init(int &pos,int l,int r)
{
    int j=log2(r-l+1);
    if(f[l][j]>f[r-(1<<j)+1][j]) pos=id[l][j];
    else pos=id[r-(1<<j)+1][j];
}//这里都懂吧,KMQ的查询
int main()
{
    n=scan();m=scan();L=scan();R=scan();
    FOR(i,1,n)
    {
        sum[i]=scan();sum[i]+=sum[i-1];//前缀和
        f[i][0]=sum[i];
        id[i][0]=i;//位置的处理
    }
    FOR(j,1,20)
    {
        FOR(i,1,n)
        {
            if(i+(1<<j)-1>n) break;
            if(f[i][j-1]>f[i+(1<<(j-1))][j-1]) f[i][j]=f[i][j-1],id[i][j]=id[i][j-1];
            else f[i][j]=f[i+(1<<(j-1))][j-1],id[i][j]=id[i+(1<<(j-1))][j-1];
        }//这里其实就是KMQQAQ
    }
    FOR(i,1,n-L+1)//入坑silasila......
    {
        int l=i+L-1; int r=min(i+R-1,n),pos=0,u=i;//随便给pos一个值
        init(pos,l,r);//pos在这里赋好了值
        tmp=(s1){u,l,r,pos};//然后加入四元组吧..
        q.push(tmp);//入队
    }
    while(m--)
    {
        tmp=q.top(); q.pop();//弹出顶端,即最优解
        int u=tmp.u,l=tmp.l,r=tmp.r,tps=tmp.pos,pos=tmp.pos;//去寻找次
//优解
        ans+=sum[tps]-sum[u-1];
        if(tps>l)
        {
            init(pos,l,tps-1);tmp=(s1){u,l,tps-1,pos};q.push(tmp);
        }
        if(tps<r)
        {
            init(pos,tps+1,r);tmp=(s1){u,tps+1,r,pos};q.push(tmp);
        }
    }
    printf("%lld",ans);
    return 0;
}
代码戳这里
原文地址:https://www.cnblogs.com/KSTT/p/10380545.html