好久沒有寫博客了,今天再寫一篇。還是先看題:
試題描述
|
阿明是一名推銷員,他奉命到螺絲街推銷他們公司的產品。螺絲街是一條死胡同,出口與入口是同一個,街道的一側是圍牆,另一側是住戶。螺絲街一共有 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了!!