NOIP2015普及组第四题推销员
好久没有写博客了,今天再写一篇。还是先看题:
试题描述
|
阿明是一名推销员,他奉命到螺丝街推销他们公司的产品。螺丝街是一条死胡同,出口与入口是同一个,街道的一侧是围墙,另一侧是住户。螺丝街一共有 N 家住户,第 i 家住户到入口的距离为 Si 米。由于同一栋房子里可以有多家住户,所以可能有多家住户与入口的距离相等。阿明会从入口进入,依次向螺丝街的 X 家住户推销产品,然后再原路走出去。阿明每走 1 米就会积累 1 点疲劳值,向第 i 家住户推销产品会积累 Ai 点疲劳值。阿明是工作狂,他想知道,对于不同的 X,在不走多余的路的前提下,他最多可以积累多少点疲劳值。
|
输入
|
第一行有一个正整数 N,表示街住户数量,接下来一行有 N 个正整数,其中第 i 个整数 Si 表示第 i 家住户到入口距离保证 S1<=S2<=S3....<10 的 8 次方。接下来一行有 N 个整数,其中第 i 个整数 Ai 表示向第 i 个住户推销产品会积累疲劳值。保证 Ai<=10 的 3 次方。
|
输出
|
输出 N 行,每行一个正整数,其中第 i 行整数表示当 x=i,阿明积累的疲劳值。
|
输入示例
|
5
1 2 3 4 5 1 2 3 4 5 |
输出示例
|
15
19 22 24 25 |
其他说明
|
数据范围:1<= N <= 1000000。
|
针对每一个X,我在考场当时想能不能用贪心,每一次都选取当前那一个疲劳值最大的房子,但不知怎么了,认为不行,觉得应该用DP做,然后就当场懵逼了。现在想何尝不可以呢?证明很简单,只要用反证法即可。注意,题目中所说不走多余路的意思就是推销x的房子的时候必须一口气走完,不能绕来绕去,这样就更能说明了这样的贪心策略是可以的。本题为去年普及组最后一题,当时没做出来,现在就秒杀了,感觉自己有很大进步。
贪心策略想好了,下面就开始实现,如何选取当前那一个疲劳值最大的房子呢?对于一个没来过的房子(偏向右端),价值就是s[i]*2+a[i],这里指的是第一个,接下来就不一样了,每一次接下来选择的都是阿明走过最远房子的左端或右端中价值最大的房子。对于左端,因为不能走多余的路,所以价值就是a[i],右端呢,需要加上往返的距离,所以价值就是a[i]+(s[i]-now)*2,其中now指的就是阿明走过最远房子的位置,然后取一个最大值即可。如何快速找呢?很容易就能想到可以用一个最大堆来实现,就用STL里的priority_queue实现了,优先队列要声明两个,一个维护now左端的房子,另一个维护now右端的房子。注意开始时now=0;
想这么多就差不多可以开始写了,首先我先无脑写了一个结构体node,里面包含着四种信息(编号,a[i],s[i],价值),然后就把结构体读入了,但是到后来才知道:价值是会变的,在读入中盲目就读入价值是不行的……之后终于写完了,提交到oj上,结果时间超限,多交了几次,有一回999ms卡过了,我就意识到应该是读入的问题,于是就把scanf改成了read,然后就AC了。
下面是代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstdlib> 4 #include<queue> 5 #include<cmath> 6 #include<algorithm> 7 #include<cstring> 8 using namespace std; 9 const int maxn=1000010; 10 int n,flag[maxn],ans,s[maxn],a[maxn],now; 11 struct node{ 12 int s,val,num; 13 bool operator < (const node &b)const //重载运算符,目的是帮助优先队列排序 14 { 15 if(val!=b.val)return val<b.val; 16 return s<b.s; 17 } 18 }; 19 node MAX(node a,node b){return a<b ? b :a;} 20 int read() 21 { 22 int x = 0, f = 1; char c = getchar(); 23 while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); } 24 while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); } 25 return x * f; 26 } 27 int main() 28 { 29 priority_queue <node> Q1,Q2; 30 n=read(); 31 for(int i=1;i<=n;i++)s[i]=read(); 32 for(int i=1;i<=n;i++) 33 { 34 a[i]=read(); 35 Q2.push((node){s[i],a[i]+s[i]*2,i});//由于初始位置是now,所以把所有都加入Q2,表示在右端 36 } 37 for(int i=1;i<=n;i++) 38 { 39 node t1=(node){0,0,0},t2=(node){0,0,0}; 40 if(Q2.size()) 41 { 42 t1=Q2.top();Q2.pop(); 43 while(t1.s<=now && Q2.size())t1=Q2.top(),Q2.pop();//把Q2中位于now左边的都删了 44 t1.val-=2*now; 45 } 46 if(Q1.size())t2=Q1.top(),Q1.pop(); 47 node t0=MAX(t1,t2); 48 if(t0.num==t2.num && t1.num)t1.val+=2*now,Q2.push(t1);//注意在搁回Q2时,不要忘记将点权在加回2*now 49 else if(t0.num==t1.num)Q1.push(t2);//选取t0的操作是选取now左端和右端的点权最大值,在选取之后务必要把那个没有被选上的房子搁回队列中,要不就无视那房子了 50 flag[t0.num]=1; 51 ans+=t0.val; 52 printf("%d\n",ans); 53 if(t0.s>now) 54 { 55 now=t0.s; 56 for(int j=1;j<=t0.num;j++)//将位于now左边的全部加入Q2 57 if(!flag[j]) 58 Q1.push((node){s[j],a[j],j}); 59 } 60 } 61 return 0; 62 }
调了很久才叫真正AC。
有几处小细节,是我在研读他人代码时发现的,我自己编的数据是这样的:
5
1 2 3 4 5
10 10 10 20 2
以及它的对称:
5
1 2 3 4 5
2 20 10 10 10
就是因为这两组数据卡爆了多少人,其中包括幸京睿,汪梁森,还有王子健。(然而李琦煜和吕紫剑的程序能完美解决这个问题)问题主要是出在第48,49行代码上,具体意思请看旁边注释。由此可见,小细节还是十分坑爹的,关于如何解决这些问题的话,还是要多做题吧。
在这里我来吐槽一下NOIP普及组给的测试数据究竟有多水,本人在之前有N多个bug的前提下仍旧AC了!!