[NOIP2015][Vijos1977]推销员(heap)

题目描述

传送门

题解

感觉vijos上的数据比较强,Po的代码在别的网站上过了但是在这里总是wa一组。最后好像是因为一个奇怪的边界问题。
分析题目我们可以发现,假设说推销i个人的时候最远走到了第x个点,那么推销i+1个人的时候就有两种选择:在1~x-1这些点中选一个权值的点,或者在x+1~n这些点中选一个权值+多出来的路程最大的点。这两个选择中选比较大的那个。如果相等的话,选的点越靠后越好一定是更优的。
到这一步之后就是线段树裸题啊对不对,不过更聪明一点可以直接用两个大根堆来实现。也就是说,左边的堆权值就是点的权值,右边的堆权值是点的权值+路径长度。由于可以证明最远到达的点一定是不降的,所以可以一直从左向右移动就可以了。每一次通过打标记使左边的堆里有效元素只有1~x-1中的点,右边的堆里有效元素只有x+1~n中的点。
每个点只会入堆出堆至多两次,时间复杂度 O(nlogn)

我刚开始有一个不是很靠谱的思路:枚举每一个点,假设某一个点x是它前缀和里的第k大,那么在推销k~n个人的时候都至少能选到点x对吧?而且那么在线段树里把k~n这一段都max一下x。这样就能 O(nlogn) 求出来当询问1…n个人的时候最远走到了哪个点。那么现在的问题就是对于一个点x,求其前缀前k大权值的和。这玩意用一个splay吧?不过我懒得写了。。感觉理论上说是对的。

代码

#include
#include
#include
#include
using namespace std;
#define N 100005

int n,Max,maxp,point;
int s[N],a[N],pt[N],ans[N];
bool vis[N];
struct hp
{
    int val,loc;
    bool operator < (const hp &a) const
    {
        return a.val>val;
    }
};
priority_queue  q;
priority_queue  p;


int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;++i) scanf("%d",&s[i]);
    for (int i=1;i<=n;++i) scanf("%d",&a[i]);
    for (int i=1;i<=n;++i)
        if (a[i]+2*s[i]>=Max)
        {
            Max=a[i]+2*s[i];
            maxp=i;
        }

    for (int i=1;i<=maxp;++i)
    {
        hp now;now.loc=i,now.val=a[i];
        q.push(now);
    }
    for (int i=maxp+1;i<=n;++i)
    {
        hp now;now.loc=i,now.val=a[i]+s[i]*2;
        p.push(now);
    }
    point=maxp+1;vis[maxp]=true;
    ans[1]=Max;pt[1]=maxp;
    for (int i=2;i<=n;++i)
    {
        hp l,r;l.val=l.loc=r.loc=r.val=0;
        if (!q.empty()) l=q.top();
        if (!p.empty()) r=p.top();
        while (vis[l.loc])
        {
            l.val=l.loc=0;
            if (!q.empty()) q.pop(),l=q.top();
        }
        while (vis[r.loc])
        {
            r.val=r.loc=0;
            if (!p.empty()) p.pop(),r=p.top();
        }
        if (!r.val||(l.val!=0&&ans[i-1]+l.val>ans[i-1]+r.val-2*s[pt[i-1]]))
        {
            if (!q.empty()) q.pop();
            vis[l.loc]=true;
            pt[i]=pt[i-1];
            ans[i]=ans[i-1]+l.val;
        }
        else
        {
            if (!p.empty()) p.pop();
            vis[r.loc]=true;
            pt[i]=r.loc;
            ans[i]=ans[i-1]+r.val-2*s[pt[i-1]];
        }
        for (int j=point;j<=pt[i];++j)
        {
            hp now;now.loc=j,now.val=a[j];
            q.push(now);
        }
        point=pt[i]+1;
    }
    for (int i=1;i<=n;++i) printf("%d\n",ans[i]);
}

总结

①分析问题的性质太关键了。往往可以把复杂的问题简化。
②感觉这题有一点点递推的思路。都是从上一个经过决策转移过来。

你可能感兴趣的:(题解,NOIP,堆)