NOIP2015 PJ 4 salesman

今年普及组的第四题似乎难度可以比肩提高组前两天的题目。
【题意】
给出一个S数组和一个A数组,并且选n次,第i次选i个元素,使得这i个元素中S的最大值*2+每一个选择的元素的A值最大。而且,输入给定的S,满足S1<S2<S3<……<Sn。
【分析】
首先,可以证明,第i次选择只需要第i次选择再选择一个即可。这不难证明,而且是显而易见的。
其次,我们可以发现,每一次的选择,都只需要比较在最远一个已选择的前面的选择,和后面的选择即可。
后面的选择,可以只需要维护在某个坐标后面,令得Si*2+Ai最大的相应的i即可。这可以通过一遍预处理得到,注意要逆序。
那,前面的选择呢?前面的选择,要求维护一个区间内的最大值,而且还会有的时候加上几个数,删去一个数。这样的操作,让我们想到了什么?单调队列!这样的话,我们就可以在O(n)的总时间内做到解决该题了。但是单调队列写起来相对比较麻烦,特别是相应的删除的操作,可能写起来比较麻烦。
所以,我们应该要换那么一个数据结构。首先,加上几个数,可以等价于多次加上一个数,所以说,我们可以用胜者树做。删除的数,可以等价于将某个数更改为一个很小的数(比如说-INF,这样的话就肯定不会成为最大值)。所以合起来的时间复杂度是O(Nlog2N)。当然,胜者树不要写错,特别是用位运算的时候,就更不要搞错了方向(我就错了几次)。别忘记在某个新的在当前最远元素后面的元素选了之后还要再加上一些元素进胜者树里。
其次,我们还可以用线段树,但是太过于麻烦了……
回头来看,我们可以看到在题目中有一个较小的数据:10^3。这才1000啊!而回想一下,我们要维护的就只是Ai的最小值,那么用计数排序的思想,我们可以将全部的最远的已选元素之前的Ai全部放进一个数组中,然后从这个数组的最大一项开始往下找,一直找到的第一个,就是相应的Ai的最大的值!算一下,我们会发现,运算量大约是10^8,刚刚好不超。如果这个值很大又怎样呢?我们可以用分块的思想,比如说10^3变成了10^6,我们可以将10^6再分块成sqrt(10^6)=10^3个,每个块10^3个,然后维护最大值,就只需要10^3的计算,就可以在每一次的插入和删除之后轻易地重新统计到最大值,而且相对来说还是挺好写的。可以说,分块法是骗分的巧妙方法,可以在代码不算很长的情况下,就得到更好的时间复杂度。一般来说,分块甚至可以骗到本属于平衡树、线段树、动态树、树链剖分、动态规划的分!
继而,我们还可以用排序,设一个T数组,将T数组设为从大到小排序后的A数组,也可以有两种决策:在T中选,以及在已选的最远元素之后找。因此,这样也不难写,只需要一次sort,接着维护一个T数组的最前的未选择的下标,不断地选,不断地比较即可。

【方案总结】
总方针:维护前面Ai的最大,维护后面Ai+Si*2的最大。
做法1:胜者树、线段树、计数排序、分块计数(相较好想)
做法2:排序(相较好写)

【代码1】

# include <cstdio>
using namespace std;
const int MAXN = 100010;
const int INF = 10000000;

int n;
int tree[MAXN << 2];
int d[MAXN];
int v[MAXN];
int maxp[MAXN];
int UnderP;
int NowFar;
int NowTired;

inline int GetMaxPosinT(){ return tree[1]; }
void Update(int pos,int x){
v[pos] = x;
int Bpos = pos + UnderP;
tree[Bpos] = pos;
for (Bpos >>= 1;Bpos != 0;Bpos >>= 1)
tree[Bpos] = (v[tree[Bpos<<1]] > v[tree[(Bpos<<1)+1]] ? tree[Bpos<<1] : tree[(Bpos<<1)+1]);
}

int main(){

scanf("%d",&n);
for (UnderP = 1;UnderP < n;UnderP <<= 1);

for (int i=0;i<n;++i) scanf("%d",&d[i]);
for (int i=0;i<n;++i) scanf("%d",&v[i]);

v[n] = -INF;

maxp[n-1] = n-1;
for (int i=n-2;i>=0;--i){
int lasttired = d[maxp[i+1]]*2 + v[maxp[i+1]];
int thistired = d[i]*2 + v[i];
maxp[i] = (lasttired > thistired ? maxp[i+1] : i);
}

NowFar = maxp[0];
NowTired = v[maxp[0]];
printf("%d\n",d[NowFar]*2+NowTired);

for (int i=0;i<=UnderP*2;++i) tree[i] = n;

for (int i=0;i<maxp[0];++i){
Update(i,v[i]);
}

Update(maxp[0],0);

for (int i=1;i<n;++i){
int Left = GetMaxPosinT();
if (NowFar != n-1){
int Right = maxp[NowFar+1];
if (v[Left] > v[Right]+(d[Right]-d[NowFar])*2){
NowTired += v[Left];
Update(Left,0);
}
else{
NowTired += v[Right];
for (int j=NowFar+1;j<Right;++j){
Update(j,v[j]);
}
NowFar = Right;
}
}
else{
NowTired += v[Left];
Update(Left,0);
}
printf("%d\n",d[NowFar]*2+NowTired);
}

return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值