题目描述
题解
感觉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<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
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 <hp> q;
priority_queue <hp> 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]);
}
总结
①分析问题的性质太关键了。往往可以把复杂的问题简化。
②感觉这题有一点点递推的思路。都是从上一个经过决策转移过来。