我現在重新簡要的闡述題意:
有兩個人,第一個人會任意的在盤子中放置蘑菇、第二個人會吃掉蘑菇;
給你\(N\)個時間點(每個時間點之間間隔一樣久)盤子中的蘑菇數量,請計算第二個人至少吃了幾個蘑菇。
第二個人有兩種吃蘑菇的策略,分別如下:
1. 他在任意時間吃掉任意數量的蘑菇。
2. 只要盤子裡還有蘑菇,他就會以固定的速度吃蘑菇;否則,他就會暫停吃蘑菇。
請分別針對這兩種策略計算他至少吃了幾個蘑菇。
有幾個點導致題意難以理解:
1. (我自己一開始)以為那\(N\)個數字是被加進來的蘑菇數量,所以完全不理解。
2. (在FB看到其他人反應)題目中特別點到那\(N\)個時間點間隔\(10\)秒,讓他們以為第二個人吃的速度必須是每秒整數個。
不過,一旦理解了題意,這題就迎刃而解了!
對於第一種策略:
沒什麼特別的,只有盤子中蘑菇數量減少時,當做那減少的量被他吃掉了。
而第二種策略:
找出盤子中蘑菇數量減少最多的一次,當做他吃蘑菇的速度,
然後從頭到尾掃過去計算他吃了多少。
(注意盤子蘑菇不夠時就不會吃了)
時間複雜度:\(O(N)\)然後從頭到尾掃過去計算他吃了多少。
(注意盤子蘑菇不夠時就不會吃了)
AC code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | #include <bits/stdc++.h> #define pb push_back #define F first #define S second #define SZ(x) ((int)(x).size()) #define MP make_pair using namespace std; typedef long long ll; typedef pair<int,int> PII; typedef vector<int> VI; int m[10000],N; int main() { int NumberOfTestcases; scanf("%d",&NumberOfTestcases); for(int CaseNumber=1;CaseNumber<=NumberOfTestcases;CaseNumber++) { printf("Case #%d: ",CaseNumber); scanf("%d",&N); for(int i=0;i<N;i++) scanf("%d",m+i); int ans1=0,ans2=0,big=0; for(int i=1;i<N;i++) ans1+=max(0,m[i-1]-m[i]); for(int i=1;i<N;i++) big=max(big,m[i-1]-m[i]); for(int i=0;i<N-1;i++) ans2+=min(big,m[i]); printf("%d %d\n",ans1,ans2); } return 0; } |
對於每個顧客,一旦有空閒的理髮師時,他就會由編號最小(而不是花費時間最少)的理髮師負責理髮。
請問第\(N\)位顧客會由編號多少的理髮師負責?
(\(1\leq{N}\leq10^9,1\leq{B}\leq10^3,1\leq{M}_k\leq10^5\))
因為\(N\)很大,所以一定要把它從複雜度當中壓掉。
正確的策略:
先利用二分搜計算第\(N\)個顧客開始剪頭髮的時間,(對於每個時間,可以\(O(B)\)判斷是否剪到頭髮)
然後算出他是那個時間點的第\(x\)位顧客,找到第\(x\)位在這個時間點完成前一項工作的理髮師即可。
時間複雜度:\(O(B\ log(N\times{M}_k))\)然後算出他是那個時間點的第\(x\)位顧客,找到第\(x\)位在這個時間點完成前一項工作的理髮師即可。
AC code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | #include <bits/stdc++.h> #define pb push_back #define F first #define S second #define SZ(x) ((int)(x).size()) #define MP make_pair using namespace std; typedef long long ll; typedef pair<int,int> PII; typedef vector<int> VI; int M[10000],B,N; ll count(ll t) // count the number of customers with started cut(include) { if(t<0) return 0ll; ll re=0; for(int i=1;i<=B;i++) re+=t/M[i]+1; return re; } int main() { int NumberOfTestcases; scanf("%d",&NumberOfTestcases); for(int CaseNumber=1;CaseNumber<=NumberOfTestcases;CaseNumber++) { scanf("%d%d",&B,&N); for(int i=1;i<=B;i++) scanf("%d",M+i); ll l=0,r=(ll)1e15,m; while(l<=r) { m=(l+r)/2; if(count(m)<N) l=m+1; else r=m-1; } // target starts cut at time "l" printf("Case #%d: ",CaseNumber); int tmp=count(l-1); for(int i=1;i<=B;i++) { if(l%M[i]==0) { tmp++; if(tmp==N) { printf("%d\n",i); break; } } } } return 0; } |
這題特殊的是他的範圍:
Small是\(T\leq100,N\leq15\)
Large是\(T\leq14,N\leq3000\)
Large是\(T\leq14,N\leq3000\)
Large的\(T\)縮小了,這顯然就暗示我們:複雜度會卡在邊緣!
先講Small,只要\(2^N\)枚舉所有點的子集之後做凸包即可!(注意要保留線上的點)
總時間複雜度:\(O(N\times2^N)\)
寫得有點亂不過至少能過Small的code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | #include <bits/stdc++.h> #define pb push_back #define F first #define S second #define SZ(x) ((int)(x).size()) #define MP make_pair using namespace std; typedef long long ll; typedef pair<int,int> PII; typedef vector<int> VI; struct PT{ int x,y,id; PT(int _x=0,int _y=0): x(_x),y(_y) {} PT operator -(const PT&b)const { return PT(x-b.x,y-b.y); } ll operator %(const PT&b)const // cross { return 1ll*x*b.y-1ll*y*b.x; } bool operator < (const PT&b)const { if(x!=b.x) return x<b.x; return y<b.y; } }dot[100]; int ans[100]; int N; vector<PT> use; PT tmp[100],st[100]; int top; void check() { int num=0; for(PT x:use) tmp[num++]=x; assert(num==SZ(use)); std::sort(tmp,tmp+num); top=-1; for(int i=0;i<num;i++) { while(top>=1 && ((st[top]-st[top-1])%(tmp[i]-st[top]))<0) top--; st[++top]=tmp[i]; } for(int i=0;i<=top;i++) ans[st[i].id]=min(ans[st[i].id],N-num); top=-1; for(int i=num-1;i>=0;i--) { while(top>=1 && ((st[top]-st[top-1])%(tmp[i]-st[top]))<0) top--; st[++top]=tmp[i]; } for(int i=0;i<=top;i++) ans[st[i].id]=min(ans[st[i].id],N-num); } void run(int l) { if(l==N) { check(); return; } run(l+1); use.pb(dot[l]); run(l+1); use.pop_back(); } int main() { int NumberOfTestcases; scanf("%d",&NumberOfTestcases); for(int CaseNumber=1;CaseNumber<=NumberOfTestcases;CaseNumber++) { scanf("%d",&N); for(int i=0;i<N;i++) scanf("%d%d",&dot[i].x,&dot[i].y),dot[i].id=i; fill(ans,ans+N,123456); use.clear(); run(0); printf("Case #%d:\n",CaseNumber); for(int i=0;i<N;i++) printf("%d\n",ans[i]); } return 0; } |
至於Large:
對於每個被詢問的點\(A\),我們嘗試對每個不為\(A\)的點\(B\)計算線段\(AB\)為凸包上的邊時,需要砍掉多少點。
若認定線段有方向性,那麼這部分可以簡化成尋找一個點\(C\)滿足有向角\(AB\)到\(AC\)不小於\(\pi\),並最小化期間的點數。
那麼,只要以\(A\)為中心將其他的點做極角排序,上面這個步驟就可以用類似sliding window的方法快速解決!
時間複雜度:\(O(N^2logN)\)
把題目的範圍帶進去算,應該就不難理解為何會是\(T\leq14\)了。
我的AC code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | #include <bits/stdc++.h> #define pb push_back #define F first #define S second #define SZ(x) ((int)(x).size()) #define MP make_pair using namespace std; typedef long long ll; typedef pair<int,int> PII; typedef vector<int> VI; struct PT{ int x,y,id; PT(int _x=0,int _y=0): x(_x),y(_y) {} PT operator -(const PT&b)const { return PT(x-b.x,y-b.y); } ll operator %(const PT&b)const // cross { return 1ll*x*b.y-1ll*y*b.x; } ll operator *(const PT&b)const // dot { return 1ll*x*b.x+1ll*y*b.y; } ll len2()const { return x*x+y*y; } }dot[30000]; int N; const PT stu=PT(1,0); bool cmp(const PT &pa,const PT &pb) { if(stu%pa>0 && stu%pb<0) return 1; if(stu%pa<0 && stu%pb>0) return 0; if(stu%pa==0 && stu*pa>0) { if(!(stu%pb==0 && stu*pb>0)) return 1; return stu*pa<stu*pb; } if(stu%pb==0 && stu*pb>0) return 0; if(pa%pb!=0) return pa%pb>0; return pa.len2()<pb.len2(); } vector<PT> use; int main() { int NumberOfTestcases; scanf("%d",&NumberOfTestcases); for(int CaseNumber=1;CaseNumber<=NumberOfTestcases;CaseNumber++) { scanf("%d",&N); for(int i=0;i<N;i++) scanf("%d%d",&dot[i].x,&dot[i].y),dot[i].id=i; printf("Case #%d:\n",CaseNumber); if(N==1) { puts("0"); continue; } for(int i=0;i<N;i++) { int ans=123456; use.clear(); for(int j=0;j<N;j++) { if(j==i) continue; use.pb(dot[j]-dot[i]); } sort(use.begin(),use.end(),cmp); assert(SZ(use)==N-1); for(int i=0;i<N-1;i++) use.pb(use[i]); int l=0,r=0; for(;l<N-1;l++) { if(r<=l) r=l+1; while(r<l+N-1 && (use[l]%use[r]>0 || (use[l]%use[r]==0 && use[l]*use[r]>0 && use[l].len2()<use[r].len2()) )) r++; ans=min(ans,r-l-1); } printf("%d\n",ans); } } return 0; } |
賽後下載別人的解法,發現大部分人都用了
甚至dreamoon直接把
此外,他把所有極角\(+2\pi\)複製一遍並且嘗試最大化不用刪除的點,實作上比較直觀,也比較沒有奇怪的細節!
他的code我下載下來只跑了\(9\)秒,比我的code快了約\(3\)倍!!!
GCJid: jo4
這場比賽以100分 2:34:52的成績排到第305名!
A,B都解得算順利,花費的思考時間不算太長。
C就很驚險了,想了很久,決定先暴搜拿下Small,然而不想重寫凸包而使用過去的code片段的下場就是:
因為變數名稱沒改好而WA了兩次!
之後在床上思考Large,突然靈機一動想到作法爬起來寫!
倒數8~9分的時候才寫完Large,然後拿Small測...有bug!
還沒找到bug,但是發現離結束不到8分鐘,就乾脆先下載Large了XD
然後邊跑Large邊找bug,跑完先上傳...(知道這個一定會WA)
-
過不了多久,就很幸運的找到bug,原來只是個\(N=1\)的special case!
於是修完就先重傳Large~
然後拿3筆Small測,都對了 :D
就相信Large會過,等比賽結束...
最後上傳Large那時候已經只剩3分8秒了OAO
----
我在GCJ從來沒有破台過...包含qualification @@
還好C沒有別的bug,自認計算幾何很弱...
沒有留言:
張貼留言