希望1B跟C不要比這場難就好了..
話說這場的時間是星期六的早上9時, 這星期實在太辛苦了..星期六真的要睡很久就沒參加了
事後在Practice看看水平如何...
A是很簡單的一題, B要想一會兒..我覺得B在於我來說有點運氣成分, 就是不一定每次都可以想到
C直接果斷只做了C-small...C-large有空再想吧, 應該很tricky?
現場來說, 只要做到A,BC-small, 基本就1000名內 = 升級了...
Google Code Jam 2015 Round 1A
A: (Math, Greedy)
一題直接的數學題...數據也不大, 能過小的基本也能過大的...
就是給了兩個方案, 各自Greedy地計算一個數字, 然後輸出就是了
唯一令我confuse了一會的是那個"rate", 我第一眼以為是以秒算的, 那麼不能整除的話
就要 ceil 了, 但從test case來看, 這個"rate"是每10秒計的...其實也很合理
#include<bits/stdc++.h> using namespace std; int T,t1,t2,n,a[1005],r; int main(){ scanf("%d", &T); for(int qwe=1; qwe <= T;qwe++){ scanf("%d", &n); t1=t2=r=0; for(int i=0; i<n;i++) { scanf("%d", &a[i]); if(i) { r = max(r, a[i-1]-a[i]); if(a[i-1] > a[i]) t1 += a[i-1]-a[i]; } } r = max(r,0); for(int i=0; i<n-1;i++) t2 += min(a[i], r); printf("Case #%d: %d %d\n", qwe,t1,t2); } return 0; }
B: (Binary Search, Math)
這題能腦子一閃想到也算是運氣, 意思是Round 1B或C有這類題目, 我未必能想到
我直接就看大數據來想的, 但要是真的做不到large case, 應該small case有一些簡單暴試的方法
題目給了 B個髮型師 (|B| <= 1000), 每個髮型師要M_i (M_i <= 10^5) 的時間理髮
你是第N位客人 (N<= 10^9), 每一個客人會等到有任意有空的髮型師就找他理髮, 要是有數個有空的就找 ID 最小的髮型師, 問會是那一個髮型師為你理髮呢?
始且順住我的思路盡力寫下:
首先由於 N 太大, (這個N 是small / large case 都一樣的)
始且順住我的思路盡力寫下:
首先由於 N 太大, (這個N 是small / large case 都一樣的)
連 O(N)都過不了, 所以我估了數個可能的複雜度: O(B lg (N)) , O(B lg( max(M_i))) ...
於是思路就是以 |B| 為重點來開始想
於是思路就是以 |B| 為重點來開始想
"For 每一個B 做些什麼" 這樣子...好像也沒什麼想法
於是在紙上 visualize test case: 是這樣子的
這是第一個test case, 由於N = 4, 所以答案就是 B = 1
類似這樣的畫法, 另外2個test case也都畫出來了
一些 M_i 不能整除的也都亂畫了幾個
然後靈光一閃的位置到了...
我就這樣打直畫了一筆
然後開始想...這個是代表時間吧
在任意時間, 總共處理 (正在處理中的都算) 了多少位客人?
這個好像能知道的樣子
加上之前估算的複雜度, 一個字: Binary Search
然後開始細想: 最大的時間是多少? 直接想最惡劣的情形(對客人來說): 只有1個髮型師, 他還要用最久的時間處理一個人, 這樣最大的時間就是 10^9 * 10^5 = 10^14!
O(lg (10^14)) 足夠有餘!
而每一個時間, 怎樣算有多少客人已經處理?
設時間是 t , 由於我們正在處理中的都算, 所以是 Ceil( t / m_i)
總數就是 Sum (Ceil (t / m_i) ) , 這個可以用O(|B|) 算出
我們要找的是: 最少的時間令這個Sum >= N
這樣, O(B lg (N*max(M_i))) 感覺很像樣了
問題是, 現在我們有一個時間點了 (這個時間作為第N個客人, 你會被處理)
怎樣找出答案要求的髮型師 ID呢?
我的想法是: 把這個時間點再減 1 , 然後再算一下Sum (總共多少位客人在處理)
然後有以下的推論:
這個前一秒的Sum 肯定比我們以Binary Search找的小, 不然我們Binary Search 那一步就錯了 (contradiction)
那麼這一秒可以使Sum增加的原因是, 某些髮型師在這"前一秒"剛好完成手頭上的工作, 空出來了
把這2個Sum 的difference叫offset, 我們要找的髮型師就是 第 offset 個 剛好完成工作空出來的髮型師, 這些髮型師的特點是 : 他們的理髮時間 M_i 能夠整除我們Binary Search的時間-1秒
所以找第offset 個髮型師 (就是答案) 這一步還是可以O(|B|)找的...
算法也完成了
這題還是很有信心的...因為每一步都真的想得很通, 也很說服到自己再code的...
能一棍過太好了, 就是不知現場發揮有沒有這麼好= =
下面用%I64d 是因為我在Codeforces隨便submit再copy的, 我不會其它方法使code那麼好看了(排版上)
#include<bits/stdc++.h> #define LL long long using namespace std; int T; LL b,n,m[1005]; int main(){ scanf("%d", &T); for(int qwe=1;qwe<=T;qwe++){ scanf("%I64d%I64d", &b, &n); for(int i=0; i<b;i++) scanf("%I64d", &m[i]); LL lo = 0, hi = 100000000000005LL, mi, M; while(lo <= hi){ mi = lo + (hi-lo)/2; LL sum = 0; for(int i=0; i<b;i++){ sum += (LL)ceil(mi*1.0/m[i]); } if(sum >= n){ hi = mi-1; M = mi; } else lo = mi+1; } LL psum = 0; vector<int> idList; for(int i=0; i<b;i++){ psum += (LL)ceil((M-1)*1.0/m[i]); if((M-1)%m[i] == 0) idList.push_back(i+1); } LL offset = n - psum; printf("Case #%d: %d\n", qwe, idList[offset-1]); } return 0; }
C-Small: (Brute Force, Geometry)
就 Small來說, 的確比B簡單...
Large還沒想所以就不講了 (想到的話再講吧..= =)
題目是問, 給了 N 點, 求N 個數字, 第 i 個數字代表 最少要刪除多少點才能使第 i 點在convex hull 上面
Small Case 沒什麼好說的, 因為 N<=15, 直接地說是怎樣做都可以, 夠暴力就OK了
我的做法是最直接的 O(2^N * N)
實作時不想煩好像寫了 O(2^N * N^2) 的, 反正都能過就沒多想了..
Convex Hull 我用我唯一會的寫法: Monotone Chain
(有出cheat 參考網上寫法)
算法就是 bitwise地決定刪走那些點, 把剩下的點做一次Convex Hull,
看看第 i 點在不在 hull 裡面...就這樣子
Convex Hull 是O(N)的, 因為sort 那一步可以放在 bitwise brute force外面
這題很好...順路幫助我記回基本的Convex Hull寫法
明天再來看看有沒有精力想 Large吧...
#include<bits/stdc++.h> #define LL long long using namespace std; struct P{ LL x,y; P(LL x, LL y): x(x), y(y){} P(){} P operator-(P a){ return P(x-a.x, y-a.y); } double operator^(P a){ return x*a.y - y*a.x; } void eat(){scanf("%I64d%I64d", &x, &y);} }tree[20],control[20]; int T,n; vector<P> hull; bool ss(P a, P b){ return (a.x < b.x) || (a.x==b.x && a.y < b.y); } double ccw(P o, P a, P b){ return (a-o)^(b-o); } bool convexHull(int x, int s){ vector<P> src; hull.clear(); int m = 0; for(int i=0; i<n;i++) if(x & (1<<i)) src.push_back(tree[i]); for(int i=0; i<src.size(); i++){ while(m >= 2 && ccw(hull[m-2], hull[m-1], src[i]) < 0){ hull.pop_back(); m--;} hull.push_back(src[i]); m++; } for(int i=src.size()-2, t=m+1; i>=0; i--){ while(m >= t && ccw(hull[m-2], hull[m-1], src[i]) < 0){hull.pop_back(); m--;} hull.push_back(src[i]); m++; } for(int i=0; i<hull.size(); i++) if(hull[i].x == control[s].x && hull[i].y == control[s].y) return true; return false; } int main(){ scanf("%d", &T); for(int qwe=1; qwe<=T;qwe++){ scanf("%d", &n); for(int i=0; i<n;i++){ tree[i].eat(); control[i] = P(tree[i].x, tree[i].y);} sort(tree, tree+n, ss); printf("Case #%d:\n", qwe); if(n<=2){ for(int i=0; i<n;i++) printf("0\n"); } else{ for(int s=0; s<n;s++){ int ans = 1<<28; for(int i=0; i< (1<<n); i++){ if(i&(1<<s)==0) continue; if(convexHull(i, s)){ int tmp=0; for(int j=0; j<n; j++) tmp += !(i&(1<<j)); ans = min(ans, tmp); } } printf("%d\n", ans); } } } return 0; }
C-Large: (Geometry)
糾纏了幾日, 終於完成了這題
果然是C-Large, 基本上要是即場做的話還是會果斷放棄
算法倒也不算複雜, 但重溫/新學了一些做Geom的技巧
算法倒也不算複雜, 但重溫/新學了一些做Geom的技巧
思路是這樣的...還是先從數據量估算複雜度開始
O(n^2) 是絕對可行的, 但不太可能以n^2 完成...
然後發現O(n^2 lg(n)) 也非常足夠
把這個複雜度Bare in mind, 再想想實際上如何入手
最直接想到的是, 一點要成為convex hull上的一點, 以它跟左右兩個hull點連起來
這一點 "另外一邊"的所有點都要刪去
推而廣之, 一點跟任何另外一點連成一線, 以這條線為分界線
刪除線隨便一邊的點, 另一邊+線上的點就可以成為hull
這樣的話 把全部2點都連起來作分界線試一下, 每條線有2個可能性 (刪去隨便一面的點)
這步至少要O(n^2)
剩下的 O(lg n)...我懂的也只有Binary Search了
說起來, 這種情況下bsearch能起作用的:
假設我們有方法把點排序, 然後用bsearch找出一個連續的範圍, 裡面的點是都要刪去的
這樣的話問題就解決了...
high level 地說:
- for 每一點 i , O(n)
- 用某種次序把點排序 O(n lg n)
- for 另一個點 j , i != j O(n)
- bsearch 要刪去的點的range O(lg n)
- 比較2種可能性那種較好 O(1)
總算起來就是O(n^2 lg n)了..
問題是怎樣排序了, 點排序的話不是先x 後y, 就是sort by angle吧..以我認識來說=.=
這題是sort by angle! 可是我沒code過, 也不太了解實作上怎樣做
也不能直接用cross product去排序, 因為 >= 180度的有負數 / 0, 根本sort不了
經Peter神提點, 原來大家都是用atan2()這個function來算角度再sort的...
所以所謂sort by angle (以某點為origin) 就是先O(n) 用atan2() 算一下角度
如果是負數的話要加 2*PI 把角度變回正數, 這樣atan2()的range是 [0, 2*PI)
Sort完後再來想是不是能利用來bsearch了
假設現在是用點 i 作origin, 我們要找最少要刪多少點才能使 點 i 在hull上
那麼 點i 連到點 j 的線, 角度是 angle (j)
那麼 點i 連到點 j 的線, 角度是 angle (j)
但還有另一邊, 另一邊的角度是 angle(j) - PI, 如果是負數的話也一樣加上2*PI
現在有2個角度, 我們分一下大小, 所以我們有了一個range, 這個range 剛好是 PI 度 (一條直線)
我們可以bsearch 在這個range內的點, 要小心的是不包括在線上的點
我們可以bsearch 在這個range內的點, 要小心的是不包括在線上的點
假設我們bsearch了在這個range內的點是 s, 我們就可以比較2種可能性了:
把這s 個點刪去, 代表 剩下的點作一個hull (點 i 是origin, 肯定在線上=hull上)
把這s 個點刪去, 代表 剩下的點作一個hull (點 i 是origin, 肯定在線上=hull上)
或是把 n - s - 線上的點 刪去, 代表用這s點和線上的點作hull (點 i 是origin, 肯定在線上=hull上)
這樣以點 i 作origin, 把所有點 j 的2種可能性都比較完就能知點答案
for 每一個點\i 都這樣做 就好了
for 每一個點\i 都這樣做 就好了
然後當然還是WA了, 搞了很久還是不知道為什麼
終於發現原來是精度問題, 太久沒做連精度問題都忘了怎樣處理
終於發現原來是精度問題, 太久沒做連精度問題都忘了怎樣處理
找了small case的一個test case試, 發現 在1e-7的位置會出現問題
所以先設eps 為 1e-7, 然後sorting 和bsearch時 (總之要比較2個double的時候)
便要考慮eps:
便要考慮eps:
feq(x,y) := fabs((x)-(y)) <= eps
flt (x,y) := ((x)+eps) < (y)
flt (x,y) := ((x)+eps) < (y)
fle(x,y) := ((x)+eps) <= (y)
我大約記得是這樣吧, 也不太肯定, 總之這樣之後就AC了...
然後實作上首先是:
bsearch是如果是用內置的lower_bound / upper_bound
bsearch是如果是用內置的lower_bound / upper_bound
跟sort 一樣, 可以自己寫個bool 的 comp function, 我這次也寫了一個, 裡面就是用上面的flt / feq 等去比較
另外聽GG說好像有不用double的sort法, 就沒有精度問題
方向大概是是找出180度的點. 以它為pivot 把點分成 < 180度和 > 180度
然後2種點內各自用cross product sort
感覺像是 quick sort 那種先fix 死一個pivot 再分2邊處理那樣
感覺像是 quick sort 那種先fix 死一個pivot 再分2邊處理那樣
可以學習一下
後記: MS 那邊那個比賽這兩天也進行了 Round 1A / B, 打得很差, 但還是有一題值得記下的, 其實問題很有學習價值, 但平台太差, 沒有practice, 也不能賽後submit, 也沒有solution / editorial....真的很令人生氣, 看看把那唯一值得記下(AC)了的一題怎樣歸類在另外開文寫吧..
沒有留言:
張貼留言