Qiitaにはいくつか記事を書いていますが、はてなでは1年以上ぶりです。
普段はPythonばかり書いています。最近はtheanoが気に入っています。あとclickと。
かよちん生誕祭に開催されたFacebook Hacker CupのR1でなぜか全完できたので、たまには記事を書こうという気になりました。
Round 1 : 1/17 3am JST〜1/18 3am JST(24時間)
Facebook Hacker Cup 2016 Round 1
全部で4問 (15,20,25,40)。30点以上獲得すれば通過。
D以外だと2問取らないと30点にならないように出来てる。これはハードかも。
D. Boomerang Tournament (40 points)
夜中に起きたら始まってたので、とりあえず問題を4つとも読んでからお風呂でリラックス。
最後に読んだDを考え始めてて、途中まで実装して、あとはビットマスクでごにょごにょすれば行ける!とか思いながら寝落ち。
2人ずつ (nC2)、4人ずつ (nC4)、のグループで、(その中で組まれた任意のトーナメント対戦で)勝つ可能性が1つでもある者・負ける可能性が1つもない者をそれぞれ決勝まで(メモ化なりDPなりで)bitで持ち上がれば計算量は減らせそう。(bitDPか)
起き上がって実装を仕上げて、大きなテストケースでも時間に余裕があるのを確認して、brute forceな(N=8までしか行けない)コードの解と照合して、提出。
#include <iostream> #include <sstream> #include <cstdio> #include <cmath> #include <cctype> #include <algorithm> #include <vector> #include <map> #include <set> using namespace std; #define all(c) (c).begin(),(c).end() #define tr(i,c) for(typeof((c).begin()) i=(c).begin(); i!=(c).end(); i++) #define rep(var,n) for(int var=0;var<(n);var++) int N, K, P; vector<vector<int> > W; vector<int> top, bottom; vector<int> mm_top, mm_bottom; vector<vector<set<int> > > pset; vector<int> winnable, losable; void sub(int k) { tr(it, pset[K][1<<k]) { int pat = *it; mm_top[pat] = 0; mm_bottom[pat] = pat; if (k == 0) { mm_top[pat] = mm_bottom[pat] = pat; continue; } tr(it2, pset[K][1<<(k-1)]) { int mask0 = *it2; if ((pat & mask0) != mask0) continue; int mask1 = pat - mask0; if (mask0 > mask1) continue; int t0 = mm_top[mask0], t1 = mm_top[mask1]; int t0_ = 0, t1_ = 0; int b0 = mm_bottom[mask0], b1 = mm_bottom[mask1]; // ここまで負けられない int b0_ = b0, b1_ = b1; for (int i=0,m=1; i<N; ++i, m<<=1) { if (m & t0) { if (winnable[i] & t1) { t0_ |= m; top[i] = k; } } else if (m & t1) { if (winnable[i] & t0) { t1_ |= m; top[i] = k; } } if (m & b0) { if (losable[i] & b1) { b0_ &= ~m; bottom[i] = min(bottom[i], k); } } else if (m & b1) { if (losable[i] & b0) { b1_ &= ~m; bottom[i] = min(bottom[i], k); } } } int t = t0_ | t1_; int b = b0_ | b1_; mm_top[pat] |= t; mm_bottom[pat] &= b; } } } int main() { map<int,int> _log2; _log2[1] = 0; _log2[2] = 1; _log2[4] = 2; _log2[8] = 3; _log2[16] = 4; int _rank[] = { 1, 2, 3, 5, 9 }; int _2k[] = { 1, 2, 4, 8, 16 }; // 2^k int _22k[] = { 1, 4, 16, 256, 65536 }; // 2^(2^k) pset.resize(5); for (int k=0; k<=4; ++k) { pset[k].resize(1+_2k[k]); } for (int p=1; p<65536; ++p) { int pc = __builtin_popcount(p); if (__builtin_popcount(pc) != 1) continue; switch (pc) { case 1: if (p < _22k[0]) pset[0][pc].insert(p); // fall thru case 2: if (p < _22k[1]) pset[1][pc].insert(p); // fall thru case 4: if (p < _22k[2]) pset[2][pc].insert(p); // fall thru case 8: if (p < _22k[3]) pset[3][pc].insert(p); // fall thru case 16: if (p < _22k[4]) pset[4][pc].insert(p); break; } } int _T; cin >> _T; rep(_t,_T){ cin >> N; K = _log2[N]; W.assign(N, vector<int>(N)); rep(i,N) rep(j,N) cin >> W[i][j]; P = 1 << N; mm_top.assign(P, -1); mm_bottom.assign(P, -1); top.assign(N, 0); bottom.assign(N, K+1); winnable.resize(N); losable.resize(N); rep(i,N) { int x = 0; for (int j=0,m=1; j<N; ++j,m<<=1) { if (W[i][j]) x |= m; } int y = (1 << N) - 1 - x; winnable[i] = x; losable[i] = y; } for (int k=0; k<=K; ++k) sub(k); printf("Case #%d: \n", 1+_t); rep(i, N) { int t = _rank[K - top[i]]; int b = _rank[K - (bottom[i] - 1)]; printf("%d %d\n", t, b); } } }
(おまけ)検算に使った乱暴なシミュレーション。next_permutation() で回してるのでN=8が限界。
#include <iostream> #include <sstream> #include <cstdio> #include <cmath> #include <cctype> #include <algorithm> #include <string> #include <vector> #include <list> #include <map> #include <set> using namespace std; typedef long long ll; #define all(c) (c).begin(),(c).end() #define tr(c,i) for(typeof((c).begin()) i=(c).begin(); i!=(c).end(); i++) #define rep(var,n) for(int var=0;var<(n);var++) #define found(s,e) ((s).find(e)!=(s).end()) int main(){ map<int,int> _log2; _log2[1] = 0; _log2[2] = 1; _log2[4] = 2; _log2[8] = 3; _log2[16] = 4; int _rank[] = { 1, 2, 3, 5, 9 }; int N, K; vector<vector<int> > W; int _T; cin>>_T; rep(_t,_T){ cin >> N; K = _log2[N]; W.assign(N, vector<int>(N)); rep(i,N) rep(j,N) cin >> W[i][j]; vector<int> iota(N); rep(i,N) iota[i] = i; vector<int> lo(N, K+1), hi(N, -1); do { int skip = N/2; vector<int> z(all(iota)), r(N, 0); for (int l=0,skip=1; skip<N; ++l,skip*=2) { for (int i=0; i<N; i+=skip*2) { int a = z[i], b = z[i+skip]; if (!W[a][b]) z[i] = b; ++r[ z[i] ]; } } rep(i,N) { lo[i] = min(lo[i], r[i]); hi[i] = max(hi[i], r[i]); } } while (next_permutation(all(iota))); printf("Case #%d: \n", 1+_t); rep(i,N) { printf("%d %d\n", _rank[K - hi[i]], _rank[K - lo[i]]); } } }
C. Yachtzee (25 points)
続いてC。
積分(というか三角形の面積を足しあわせていったもの)して底辺で割ればいいだけだよね。
これは一番簡単な気がする。
a〜bの積分は、(0〜b) - (0〜a) にした方が分かりやすい。
あとは精度と、時間が間に合うかというところだけ。
面積計算は、2倍にして整数 (long long) で計算すればいいかなと。最後の割り算だけdoubleで。
#include <iostream> #include <sstream> #include <cstdio> #include <cmath> #include <cctype> #include <algorithm> #include <vector> using namespace std; typedef long long ll; #define pb push_back #define all(c) (c).begin(),(c).end() #define rep(var,n) for(int var=0;var<(n);var++) int N; vector<ll> C, Cacc; vector<ll> S2; // S*2 ll W; ll f(ll x) { ll q = x / W, r = x % W; vector<ll>::iterator it = upper_bound(all(Cacc), r); int ix = (it - Cacc.begin()) - 1; ll d = r - Cacc[ix]; ll s = S2[ix] + d*d; return S2[N] * q + s; } int main(){ int _T; cin>>_T; rep(_t,_T){ cin >> N; ll a, b; cin >> a >> b; C.resize(N); Cacc.resize(N+1); S2.resize(N+1); Cacc[0] = S2[0] = 0LL; rep(i,N) { ll c; cin >> c; C[i] = c; Cacc[i+1] = Cacc[i] + c; S2[i+1] = S2[i] + c*c; } W = Cacc[N]; ll aq = a / W, ar = a % W; ll bq = b / W, br = b % W; ll ofs = W * aq; ll s2 = f(b-ofs) - f(a-ofs); // f(b) - f(a) double ans = 0.5*s2 / (b-a); printf("Case #%d: %.9f\n", 1+_t, ans); } }
B. Laundro, Matt (20 points)
Bを考えてて、洗濯フェーズの所要時間の最小化は二分探索でできるなあと思ったんだけど
その後の乾燥フェーズに投げるにあたって、洗濯フェーズだけで所要時間最小化したパターンが
乾燥も含めて最短になるのか自信がなくて、後回し(というか寝落ち)。
最小所要時間から、個々の洗濯機の最大稼働回数を求めて、priority queueに次に洗濯機が空く時刻を投入して行って、L個の洗濯物の1つ1つができるだけ早いタイミングで終わるパターンを得て*1キューに入れ、
乾燥フェーズのシミュレーション。こちらもpriority queueにM台*2の乾燥機の空くタイミングを投入。
#include <iostream> #include <sstream> #include <cstdio> #include <cmath> #include <cctype> #include <algorithm> #include <string> #include <vector> #include <deque> #include <stack> #include <queue> #include <list> #include <map> #include <set> using namespace std; typedef long long ll; #define all(c) (c).begin(),(c).end() #define rep(var,n) for(int var=0;var<(n);var++) ll L, N, M, D; vector<ll> W; bool p(ll a) { ll qs = 0; rep(i, N) qs += a / W[i]; return qs >= L; } int main(){ int _T; cin>>_T; rep(_t,_T){ cin >> L >> N >> M >> D; M = min(M, L); // 乾燥機は1e9個も要らんだろ W.resize(N); rep(i,N) cin >> W[i]; sort(all(W)); // 洗濯側の最短所要時間 a を binary search で ll lo = 0, hi = W[N-1]*L, a = hi; // assert p(lo) == false // assert p(hi) == true do { ll mi = (lo + hi) / 2; // 1e15 < 2^50 < LONG_LONG_MAX if (p(mi)) { a = hi = mi; } else { lo = mi; } } while (hi > lo+1); // 所要時間を a として、なるはやで仕上がる順に洗濯機に投入したい // q は洗濯仕上がり時刻のリスト priority_queue<ll, vector<ll>, greater<ll> > pq; rep(i,N) { ll wi = W[i]; for (ll w=wi; w<=a; w+=wi) { pq.push(w); } } vector<ll> q; rep(i, L) { q.push_back( pq.top() ); pq.pop(); } while (!pq.empty()) pq.pop(); // 乾燥機が使える時刻。普通のqueueでいいのかも priority_queue<ll, vector<ll>, greater<ll> > pq2; rep(i, M) pq2.push(0); ll ans; rep(i, L) { ll t = q[i]; ll d = pq2.top(); pq2.pop(); // first available dryer ll next = max(d, t); pq2.push(next + D); if (i == L-1) ans = next + D; } while (!pq2.empty()) pq2.pop(); printf("Case #%d: %lld\n", 1+_t, ans); } }
A. Coding Contest Creation (15 points)
(寝落ち後に)Bを後回しにしてAから。
greedyに取っていけば行けるような気もするんだけど、ミスなく書ける気がしないんだよな…
ちょっと出かけようと思って、歩いてたら頭の中でDPの表が書けたので、そのまま実装して提出。
問目が4つ組の何番目かの表を作って編集距離みたいにDP。
#include <iostream> #include <sstream> #include <cstdio> #include <cmath> #include <cctype> #include <algorithm> #include <vector> using namespace std; #define all(c) (c).begin(),(c).end() #define rep(var,n) for(int var=0;var<(n);var++) inline int min4(int a, int b, int c, int d) { return min(min(a,b),min(c,d)); } int main(){ int _T; cin>>_T; rep(_t,_T){ int N; cin >> N; // 1-100000 vector<int> D(N); // 1-100 rep(i,N) cin >> D[i]; vector<vector<int> > dp(N, vector<int>(4)); // dp[i][n], n=0,1,2,3は4つ組の何番目か, 値は編集距離 dp[0][0] = 0; dp[0][1] = 1; dp[0][2] = 2; dp[0][3] = 3; // D[0]=1 だとその前にはおけないんだけど、そのグループの後に置くのと結局同じことなので for (int i=1; i<N; ++i) { int last = D[i-1], curr = D[i], diff = curr - last; if (0 < diff && diff <= 10) { dp[i][0] = min4(dp[i-1][0]+3+0, dp[i-1][1]+2+0, dp[i-1][2]+1+0, dp[i-1][3]+0+0); // restart dp[i][1] = min4(dp[i-1][0] , dp[i-1][1]+2+1, dp[i-1][2]+1+1, dp[i-1][3]+0+1); dp[i][2] = min4(dp[i-1][0] +1 , dp[i-1][1] , dp[i-1][2]+1+2, dp[i-1][3]+0+2); dp[i][3] = min4(dp[i-1][0] +2 , dp[i-1][1] +1 , dp[i-1][2] , dp[i-1][3]+0+3); } else if (10 < diff && diff <= 20) { // 1 gap dp[i][0] = min4(dp[i-1][0]+3+0, dp[i-1][1]+2+0, dp[i-1][2]+1+0, dp[i-1][3]+0+0); // restart dp[i][1] = min4(dp[i-1][0]+3+1, dp[i-1][1]+2+1, dp[i-1][2]+1+1, dp[i-1][3]+0+1); dp[i][2] = min4(dp[i-1][0] +1 , dp[i-1][1]+2+2, dp[i-1][2]+1+2, dp[i-1][3]+0+2); dp[i][3] = min4(dp[i-1][0] +2 , dp[i-1][1] +1 , dp[i-1][2]+1+3, dp[i-1][3]+0+3); } else if (20 < diff && diff <= 30) { // 2 gaps dp[i][0] = min4(dp[i-1][0]+3+0, dp[i-1][1]+2+0, dp[i-1][2]+1+0, dp[i-1][3]+0+0); // restart dp[i][1] = min4(dp[i-1][0]+3+1, dp[i-1][1]+2+1, dp[i-1][2]+1+1, dp[i-1][3]+0+1); dp[i][2] = min4(dp[i-1][0]+3+2, dp[i-1][1]+2+2, dp[i-1][2]+1+2, dp[i-1][3]+0+2); dp[i][3] = min4(dp[i-1][0] +2, dp[i-1][1]+2+3, dp[i-1][2]+1+3, dp[i-1][3]+0+3); } else { // 前と切って(前を終わらせてから)restart dp[i][0] = min4(dp[i-1][0]+3+0, dp[i-1][1]+2+0, dp[i-1][2]+1+0, dp[i-1][3]+0+0); dp[i][1] = min4(dp[i-1][0]+3+1, dp[i-1][1]+2+1, dp[i-1][2]+1+1, dp[i-1][3]+0+1); dp[i][2] = min4(dp[i-1][0]+3+2, dp[i-1][1]+2+2, dp[i-1][2]+1+2, dp[i-1][3]+0+2); dp[i][3] = min4(dp[i-1][0]+3+3, dp[i-1][1]+2+3, dp[i-1][2]+1+3, dp[i-1][3]+0+3); } } int ans = min4(dp[N-1][0]+3, dp[N-1][1]+2, dp[N-1][2]+1, dp[N-1][3]); printf("Case #%d: %d\n", 1+_t, ans); } }
終了
蓋を開けてみれば4問とも通っていて、まさかの全完。
ただ、24時間あったから行けただけで、これが2時間半とか3時間とかだったら、焦って(途中までやっては放棄して次に行くなどして)1つも取れなかったりするんだよね…
それではRound 2でお会いしましょう!
Tシャツ取れるといいなあ(先着500名様)