Hatena::Grouptopcoder

kojingharangの日記 RSSフィード

|

2015-02-04

SRM 648 Div1 550 KitayutaMart

13:29 |  SRM 648 Div1 550 KitayutaMart - kojingharangの日記 を含むブックマーク はてなブックマーク -  SRM 648 Div1 550 KitayutaMart - kojingharangの日記  SRM 648 Div1 550 KitayutaMart - kojingharangの日記 のブックマークコメント

  • i*2^j (1≦i≦K, 0≦j) 円のりんごが 1 個ずつある。合計金額が最小になるように N 個買った時、最高値のりんごの値段を mod 10^9+7 で求めよ。
  • N, K≦10^9
  • ぬぬぬ
  • 表を 1 コずらして 2 で割ると元の表と同じになるから、そんな感じで再帰でわーっとやるのかな
  • 合計が最小になるように買うということは i*2^? を買うなら I*2^? (I≦i) はすべて買うはずだ
  • f(N) = (合計, 最高値) として
  • f(N) = どこまでの i を買うかを K 種類ためしつつ、Sum, V = f(N-今取ったやつ) として (Σi+Sumが最小になるやつ, その時のV)
  • でなんか規則的だから調べる i は実はすごく狭いのかな...
  • わからん
  • あとで
  • 最高値が V のときに買った総数 n_upto(V) は規則的ゆえに分かるので、V で二分探索すればいいのか
  • (N,K)=(1000000000,1) のとき V が大きすぎてだめだ...
  • 最高値が K+1 以上のときは K のりんごは必ず L 個買うとする
  • i*2^j (1≦i≦K, 0≦j≦L) の KL 個も必ず買うことになる
  • L は、1 ≦ N-KL ≦ 最高値が K のときに買う個数 な最小の L がいいっぽい
  • 例えば (N,K)=(41,10)だと n_upto(10)=18なので (L,残り)=(3,11),(4,1)がありうる
  • 答えは n_upto(X)≧残り な最小の X について max(K*2^L, X*2^L (ずれた分) )ということで
  • L=3 なら X=7, 答え=56
  • L=4 なら X=1, 答え=80
  • ...みたいに, うまく証明できないけど L は小さい方が良さそう
  • (詳しくは http://dojiboy9.atspace.cc/contest/view-post.php?p=kitayutamart に書かれているのかもしれない)
  • ということでそんな感じで書いた
  • Accepted in practice
  • もやもやが残る...
// num of cells which value <= v
ll n_upto(ll v) {
	ll ans = 0;
	while(v) {
		ans += v;
		v/=2;
	}
	return ans;
}

ll lb(int N, int K) {
	ll lo=0, hi=K;
	// n_upto(lo)<N<=n_upto(hi)
	while(lo+1<hi) {
		ll mid = (lo+hi)/2;
		if(N<=n_upto(mid)) hi=mid; else lo=mid;
	}
	return hi;
}

class KitayutaMart {
	public:
	int lastPrice(int N, int K) {
		ll countWithinK = n_upto(K);
		ll fillCount = N - countWithinK;
		ll fillLines = (fillCount + K - 1) / K;
		ll restCount = N - fillLines*K;
		return (modll(2)^fillLines) * lb(restCount, K);
	}
};


SRM 648 Div1 250 AB

13:30 |  SRM 648 Div1 250 AB - kojingharangの日記 を含むブックマーク はてなブックマーク -  SRM 648 Div1 250 AB - kojingharangの日記  SRM 648 Div1 250 AB - kojingharangの日記 のブックマークコメント

  • A,Bからなる長さ N の文字列 s を、s[i]='A', s[j]='B', i<j な (i, j) が K 個になるように作れ。無理なら "" を返す。
  • 2≦N≦50, 0≦K≦N(N-1)/2
  • BBB があったとして、そこに A を入れる操作を考えると
  • BBBA なら +0
  • BBAB なら +1
  • BABB なら +2
  • ABBB なら +3
  • と、割と自由度が高く数字を増やせることがわかる。
  • B が M 文字あるところに A を 1 個入れると 0〜M が作れる。
  • 一度置いた A は次に置く A にとっては無視できるのでなんか作りやすそうだ。
  • B の個数 0〜N それぞれについて、K を貪欲に減らしていきつつどこにAを入れるかを決めていけばよさそう。
  • それで無理なら "" を返す
  • 全 N, K について検証してから submit (心の平静が訪れた)
  • Accepted
class AB {
	public:
	void check(string s, int N, int K) {
		assert(N==s.size());
		REP(i, N) RANGE(j, i+1, N) if(s[i]=='A'&&s[j]=='B') K--;
		assert(K==0);
	}
	string createString(int N, int K) {
		REP(b, N+1) {
			int k = K;
			int a = N-b;
			map<int, int> bn;
			REP(i, a) {
				int take = min(k, b);
				bn[take]++;
				k-=take;
			}
			if(k==0) {
				string s;
				int ob=0;
				FOR(e, bn) {
					while(ob<e->first) {
						s+="B";
						ob++;
					}
					REP(j, e->second) s+="A";
				}
				assert(N==s.size());
				reverse(ALL(s));
				check(s, N, K);
				return s;
			}
		}
		return "";
	}
};

2015-01-21

Facebook Hacker Cup 2015 Round1 D. Corporate Gifting

00:05 |  Facebook Hacker Cup 2015 Round1 D. Corporate Gifting - kojingharangの日記 を含むブックマーク はてなブックマーク -  Facebook Hacker Cup 2015 Round1 D. Corporate Gifting - kojingharangの日記  Facebook Hacker Cup 2015 Round1 D. Corporate Gifting - kojingharangの日記 のブックマークコメント

  • 木が与えられる。隣接ノードが同じ数字にならないように、各ノードに1以上の整数を割り当てたい。値の合計の最小値を求めよ。
  • ノード数≦200,000, 1≦TestCases≦100
  • 葉に 1 を割り振ってボトムアップで決まるかと思いきやそうでもないようだ。
  • 例: 0 1 1 2 2 は 3 2 1 1 1 より 1 2 2 1 1 の方が小さい
  • 「ノードの値を v にした時、そのノード以下の合計が sum になる」という情報 f(node, v) を各ノードに持たせる木DPにしたいが、v が大きそうなので工夫したい
  • う〜む
  • 上記のようにしたとして、f(node, v) はどうやって求めるか?
  • ありうる v について f(自ノード, v) = v + Σ Min f(子ノード, x) when x!=v を求めればいい
  • とはいえ、子ノード以下が最小になるような値集合 { Min f(子ノード, x) な x } に含まれない v については when x!=v が消えて
  • 一律 f(自ノード, v) = v + Σ Min f(子ノード, x) = v + vによらない数 と表せるので
  • 結局「子ノード以下が最小になるような値集合A ∪ Aに含まれない最小の値」についてだけ調べればいい。★1
  • さらに
  • v が子ノード以下の合計値が最小になるノード値なら f(子ノード, v) == 子ノード以下の合計値のうち2番目に小さいもの
  • それ以外の v なら f(子ノード, v) == 子ノード以下の合計値の最小値
  • と、子ノード以下の合計値は 2 通りしか使われない。
  • というわけでノードごとに持っておけばいい情報は「子ノード以下の合計値が最小になるノード値minV、そのときの合計値minSum、2番目に小さい合計値otherSum」だけでいい。
  • 実際の処理も ΣminSum を計算しておいてから各 v に対して v と v==minV なら差分 otherSum - minSum を足せばいいので速そう。
  • なので、更新処理の時に新たに「2番目に小さい合計値」も求める必要がある。★2
  • Sample input を机上検討 → 良さそう (゚∀゚)キタコレ!!
  • Best2 を管理するクラスを作って、...
  • おかしい → 葉のときに最小値 1 だけしかセットしてなかった → 葉のときだけ 1, 2 をセットするようにした (後から考えるとこれでは足りず) ★3
  • N=7 までの全探索解と比較→OK
  • N=200,000 のランダム木を 100 ケース →処理時間OK
  • 満を持して提出。
  • input をダウンロード → 実行 → いきなり segfault → ファッ!?
  • 検証用に追加したコードがなんか邪魔してるのかな
  • (数分後) はっ 1本の長い木をテストしてなかった。スタックサイズが足りないかも
  • ググる。 -Wl,-stack,10485760 か。→ ld: unknown option: -stack → えっっ
  • (数分後) はっ clang は -Wl,-stack_size,10485760 なのか... → ld: -stack_size must be multiples of 4K → えっっっっ
  • -Wl,-stack_size,10485760 → ld: -stack_size must be multiples of 4K → えっっっっ
  • -Wl,-stack_size,10485760 → ld: -stack_size must be multiples of 4K → えっっっっ
  • -Wl,-stack_size,10485760 → ld: -stack_size must be multiples of 4K → えっっっっ
  • (数分後) ヒットしたページにある -Wl,-stack_size,1000000 をそのまま使う → セグフォ直る → (・・?
  • test case 1 で break してたのを忘れてた。戻して実行 →無事出力完了
  • Time expired

  • (あとで)

  • 葉のときに限らず「子ノードの値集合 ∪ (子ノードの値集合に含まれない最小と2番目に小さい値)」について f(自ノード, v) を求めるべきだったのであった。
  • 思考過程を思い出して書いてくと、確かに★2の時点で★1の前提が変わったので再評価しなければいけなかったのが分かる。
  • あと ★3 の時点で葉だけ特殊なのは何故か?と深堀りしても正解に近づいたかも。
  • ちなみに -stack_size は hex で指定するらしい。。普通に man ld に書いてあるし。
  • それか ulimit -s hard でもよかった。
  • ともかく、楽しめたので良問だったと思う。
  • Accepted in practice mode
struct Node {
	ll minV, minSum, otherSum;
	Node() : minV(0), minSum(0), otherSum(0) {}
};

struct Min2 {
	ll minK, min1, min2;
	Min2() : minK(1LL<<60), min1(1LL<<60), min2(1LL<<60) {}
	void add(ll k, ll v) {
		if(v<min1) {
			min2 = min1;
			min1 = v;
			minK = k;
		} else if(v<min2) {
			min2 = v;
		}
	}
};

ll N;
VVI g;
Node nodes[200010];

VI unused2(int idx) {
	map<int, int> vs;
	REP(i, g[idx].size()) vs[ nodes[g[idx][i]].minV ] = 1;

	VI uns;
	int un = 1;
	FOR(e, vs) {
		while(un != e->first) {
			uns.PB(un);
			if(uns.size()==2) return uns;
			un++;
		}
		un = e->first+1;
	}
	uns.PB(un);
	uns.PB(un+1);
	return uns;
}

void f(int idx) {
	REP(i, g[idx].size()) f(g[idx][i]);
	VI un = unused2(idx);
	ll minSum = 0;
	map<ll, Node> m;
	REP(i, g[idx].size()) {
		auto& n = nodes[g[idx][i]];
		Node& gr = m[n.minV];
		gr.minV = n.minV;
		gr.minSum += n.minSum;
		gr.otherSum += n.otherSum;
		minSum += n.minSum;
	}
	Min2 mn;
	REP(i, 2) mn.add(un[i], un[i] + minSum); // ここ大事!
	FOR(e, m) mn.add(e->first, e->first + minSum - e->second.minSum + e->second.otherSum);
	nodes[idx].minV = mn.minK;
	nodes[idx].minSum = mn.min1;
	nodes[idx].otherSum = mn.min2;
}

int main() {
	ios::sync_with_stdio(false);
	int Cases;
	cin>>Cases;
	REP(CaseID, Cases) {
		cin>>N;
		g = VVI(N+1);
		REP(i, N) {
			ll parent;
			cin>>parent;
			g[parent].PB(i+1);
		}
		f(1);
		ll ans = nodes[1].minSum;
		cout<<"Case #"<<CaseID+1<<": "<<ans<<endl;
	}
	return 0;
}

2015-01-16

SRM 646 Div1 600 TheGridDivOne

13:52 |  SRM 646 Div1 600 TheGridDivOne - kojingharangの日記 を含むブックマーク はてなブックマーク -  SRM 646 Div1 600 TheGridDivOne - kojingharangの日記  SRM 646 Div1 600 TheGridDivOne - kojingharangの日記 のブックマークコメント

  • 無限に広い2次元グリッドの原点に人がいて1秒で東西南北に1進める。
  • 行けない整数点が最大47個与えられる。k秒以内で行ける x 座標の最大値を求めよ。
  • 1≦k≦10^9, 行けない点の座標範囲 in [-10^9, 10^9]
  • 行けない点が少ないので座標圧縮+BFS的なもので行けそう
  • 初めて座標圧縮を書いてみる
  • なんかおかしい. そもそも圧縮済みのグラフで移動するコストを +1 にしてておかしい.
  • 移動先と移動元のマンハッタン距離を足すようにして Dijkstra に変更
  • 移動先は範囲を表すようにしたけどこれコストはどうするんだ
  • 範囲のうち左を表すようにしてみる
  • ノードに行くコストが k 未満の場合は余った分を範囲内でさらに動かせるな
  • むむむむむ合わない
  • 終了
  • (あとで)
  • そうだ行けない点と±1 をuniqueにすれば圧縮済みのグラフでもノードが1点に対応するのでコストがちゃんと求まるのだ
  • 圧縮後の一番右のノードのときだけ k の剰余分を答えに足してみる
  • failed in practice room
  • 圧縮後の一番右に限らず余剰分を足して、もしあれば右のノード座標-1まで行ける、とすれば良さそう
  • (右に行けるならそこからのやつが答えに反映されるので右-1までとしてよい)
  • 本番とその後の自力トライではできなかったものの、いつもの500とか600にしては簡単?
  • ↓passed in practice room
// Dijkstra省略
VI compress(VI v) {
	VI w;
	REP(i, v.size()) RANGE(r, -1, 2) w.PB(v[i]+r);
	sort(ALL(w));
	w.erase(unique(ALL(w)), w.end());
	return w;
}

VI toVI(vector<int> v) {
	VI w;
	REP(i, v.size()) w.PB(v[i]);
	return w;
}

class TheGridDivOne {
	public:
	int W, H;
	vector<string> m;
	int node(int x, int y) {
		return y*W + x;
	}
	int find(vector <int> X, vector <int> Y, int k) {
		VI vix = toVI(X);
		VI viy = toVI(Y);
		vix.PB(0);
		viy.PB(0);
		VI CX = compress(vix);
		VI CY = compress(viy);
		cout<<CX<<endl;
		cout<<CY<<endl;
		W = CX.size();
		H = CY.size();
		m = vector<string>(H, string(W, '.'));
		int sx, sy;
		REP(x, W) if(CX[x]==0) sx=x;
		REP(y, H) if(CY[y]==0) sy=y;
		REP(y, H) REP(x, W) REP(i, X.size()) {
			if(CX[x]==X[i] && CY[y]==Y[i]) m[y][x]='x';
		}
		m[sy][sx] = 's';
		cout<<m<<endl;
		
		Dijkstra d(W*H);
		int dx[]={0,0,1,-1};
		int dy[]={1,-1,0,0};
		REP(y, H) REP(x, W) REP(dir, 4) {
			int nx=x+dx[dir];
			int ny=y+dy[dir];
			if(0<=nx && nx<W && 0<=ny && ny<H) {
				if(m[ny][nx]=='.') {
					ll cost = abs(CX[nx]-CX[x]) + abs(CY[ny]-CY[y]);
					d.add_edge(node(x, y), node(nx, ny), cost);
				}
			}
		}
		d.run(node(sx, sy), -1);
		ll ans = 0;
		REP(y, H) REP(x, W) if(d.V[node(x, y)]<=k) {
			ll nv = CX[x] + max<ll>(0, k-d.V[node(x, y)]);
			if(x<W-1) nv = min<ll>(nv, CX[x+1]-1);
			ans = max<ll>(ans, nv);
		}
		return ans;
	}
};

2015-01-12

Facebook Hacker Cup 2015 Qualification C. Laser Maze

15:44 |  Facebook Hacker Cup 2015 Qualification C. Laser Maze - kojingharangの日記 を含むブックマーク はてなブックマーク -  Facebook Hacker Cup 2015 Qualification C. Laser Maze - kojingharangの日記  Facebook Hacker Cup 2015 Qualification C. Laser Maze - kojingharangの日記 のブックマークコメント

  • 2次元グリッドに岩と東西南北どれかを向いているレーザー砲とプレイヤーとゴールがある。
  • プレイヤーは1ステップで東西南北のうちレーザー砲や壁がない方向に進める。レーザ砲はプレイヤーが動いた直後に時計回りに90度回転して砲撃する。レーザーはレーザー砲や壁は通過しない。プレイヤーはレーザーに当たると死ぬ。
  • プレイヤーはゴールに着いてしばらく生きていたい。ゴールまで最小何ステップで行けるか。
  • 1≦H, W≦100, 1≦TestCases≦100
  • レーザー砲の向き全部の組み合わせはステップ数%4の 4 通りしかないので、プレイヤーの状態 (位置(x, y), ステップ数%4) をノードとみなして単一始点最短路問題に帰着させる。
  • Accepted
// struct Dijkstra は略
ll H, W;
vector<string> m;
vector<int> lx, ly, ldir;
int inMap(int x, int y) {
	return 0<=x && x<W && 0<=y && y<H;
}
int isFence(int x, int y) {
	char c=m[y][x];
	return c=='<' || c=='>' || c=='^' || c=='v' || c=='#';
}
int dx[] = {0,1,0,-1}; // up right down left
int dy[] = {-1,0,1,0}; // up right down left
int alive(int x, int y, int phase) {
	REP(li, lx.size()) {
		int clx=lx[li];
		int cly=ly[li];
		int cldir=(ldir[li]+phase)%4;
		while(1) {
			if(clx==x && cly==y) return 0; //shoot
			clx+=dx[cldir];
			cly+=dy[cldir];
			if(!inMap(clx, cly)) break;
			if(isFence(clx, cly)) break;
		}
	}
	return 1;
}

int canMove(int x, int y, int phase, int dir) {
	int nx = x+dx[dir];
	int ny = y+dy[dir];
	if(!inMap(nx, ny)) return 0;
	if(isFence(x, y)) return 0;
	if(isFence(nx, ny)) return 0;
	if(!alive(nx, ny, (phase+1)%4)) return 0;
	return 1;
}

ll node(int x, int y, int phase) {
	assert(0<=x && x<W && 0<=y && y<H && 0<=phase && phase<4);
	return phase*H*W + x*H + y;
}

int main() {
	//ios::sync_with_stdio(false);
	int Cases;
	cin>>Cases;
	REP(CaseID, Cases) {
		cin>>H>>W;
		m = vector<string>(H);
		REP(y, H) cin>>m[y];
		ll start;
		vector<ll> goals;
		lx.clear();
		ly.clear();
		ldir.clear();
		REP(y, H) REP(x, W) {
			if(m[y][x]=='S') start = node(x, y, 0);
			if(m[y][x]=='G') {
				REP(p, 4) goals.PB(node(x, y, p));
			}
			if(m[y][x]=='^') {
				lx.PB(x); ly.PB(y); ldir.PB(0);
			}
			if(m[y][x]=='>') {
				lx.PB(x); ly.PB(y); ldir.PB(1);
			}
			if(m[y][x]=='v') {
				lx.PB(x); ly.PB(y); ldir.PB(2);
			}
			if(m[y][x]=='<') {
				lx.PB(x); ly.PB(y); ldir.PB(3);
			}
		}
//		cout<<m<<endl;
		Dijkstra d(W*H*4);
		REP(y, H) REP(x, W) REP(p, 4) REP(dir, 4) {
			if(canMove(x, y, p, dir)) {
				int nx = x+dx[dir];
				int ny = y+dy[dir];
			    d.add_edge(node(x, y, p), node(nx, ny, (p+1)%4), 1);
			}
		}
		d.run(start, -1);
		ll ans = 1LL<<60;
		REP(ni, W*H*4) {
			REP(gi, 4) if(d.V[goals[gi]] < (1<<30)) ans=min<ll>(ans, d.V[goals[gi]]);
		}
		if(ans==1LL<<60) {
			cout<<"Case #"<<CaseID+1<<": "<<"impossible"<<endl;
		} else {
			cout<<"Case #"<<CaseID+1<<": "<<ans<<endl;
		}
//		break;
	}
	
	return 0;
}

2014-09-26

SRM 634 Div1 250 ShoppingSurveyDiv1

18:37 |  SRM 634 Div1 250 ShoppingSurveyDiv1 - kojingharangの日記 を含むブックマーク はてなブックマーク -  SRM 634 Div1 250 ShoppingSurveyDiv1 - kojingharangの日記  SRM 634 Div1 250 ShoppingSurveyDiv1 - kojingharangの日記 のブックマークコメント

  • 商品がM個、N人がそれぞれの商品を最大1コ買った。i番目の商品がs[i]個売れた場合、商品をK種類以上買った人の最小値を求めよ。
  • 1≦N≦500, 1≦M≦500, 0≦s[i]≦N, 1≦K≦M
  • 考察
  • K種類買う人にはすべての種類の商品を買ってもらおう
  • 貪欲でやってみよう
  • sを逆順ソートして, K種類以上の人→種類数が少ない人という順で貪欲にs[i]を配分していく
  • 提出
  • 貪欲じゃだめなパターンかもしれん。固い方法はないかしら
  • 答えを決め打ちしたらどうか
  • そしたら→ s'[i] = max(0, s[i]-答え) としたときに全員が K 種類未満にできるかという問題に帰着する
  • 最初のより良い答えが出る場合がある →まずい!!
  • 答えを N 通りためしたら 500^3 になってしまう→まずい
  • 答えで二分探索に変更
  • 再提出
  • 終了間際
  • 4 4 {4 3 3 3 3 2 2} で 2 って答えがでるけど最初のコードが出した 3 が正しいんじゃねえの。(→間違い
  • 最初のを提出
  • 終了
  • 4 4 {4 3 3 3 3 2 2} は 1 2 が 7 種類, 3 4 が 3 種類にできるので 2 でいいんじゃねぇかよ
  • challenge phase
  • がんばって2撃墜
  • 自分のは生き延びる
  • Failed system test
  • (あとで)
  • やはり2回目の提出のやつが正しかった。しょんぼり。
  • ↓passed in practice room
class ShoppingSurveyDiv1 {
	public:
	int minValue(int N, int K, vector <int> s) {
		int M=s.size();
		ll lo=-1,hi=N; // hi -> OK
		while(lo+1<hi) {
			ll ans=(lo+hi)/2;
//		REP(ans, N+1) {
			map<ll, ll> co;
			co[0]=N-ans;
			REP(i, M) {
				map<ll, ll> add;
				ll rest = max<ll>(0, s[i]-ans);
				FOR(e, co) {
					if(rest==0) break;
					ll use = min<ll>(e->second, rest);
	//				cout<<"use "<<use<<" for "<<e->first<<endl;
					e->second -= use;
					add[e->first+1] += use;
					rest-=use;
				}
				FOR(e, add) co[e->first]+=e->second;
	//			cout<<i<<" "<<s[i]<<" "<<co<<endl;
				assert(rest==0);
			}
			int ok=1;
			FOR(e, co) if(e->first>=K) ok=0;
			if(ok) hi=ans; else lo=ans;
		}
		return hi;
	}
};
|