Hatena::Grouptopcoder

kojingharangの日記 RSSフィード

|

2017-04-30

Google code jam 2017 Round1C B. Parenting Partnering

21:14 |  Google code jam 2017 Round1C B. Parenting Partnering - kojingharangの日記 を含むブックマーク はてなブックマーク -  Google code jam 2017 Round1C B. Parenting Partnering - kojingharangの日記  Google code jam 2017 Round1C B. Parenting Partnering - kojingharangの日記 のブックマークコメント

  • C,Jが1日のうち半分ずつ子守をする。C,Jそれぞれ子守できない予定(時間帯)が決まっている。
  • 子守役を入れ替える回数の最小値を求めよ。

  • 半分ずつ子守をしなくていいとしたときの最低交換回数 + 半分制約ありで隙間時間を割り当てた時の追加交換回数 でいいだろう
  • 予定と予定の間の隙間時間は、「前 or 次」の予定が「C or J」の 4 種類に分類できる。
  • 前と次の予定がどっちも C である隙間時間(★)に C が子守すると往復で 2 回の追加交換が必要。追加コストを払ってしまえば C, J どちらにどちらでも割り当てられるので、その隙間時間についてコストはそれ以上増えない。
  • J も同じ。
  • それ以外は追加コスト 0
  • なので
  • どちらに割り当てても良い時間の残り RestC, RestJ から
  • ★なものを追加コストがかからないように優先して割り当てる(あとで回数が問題になるので短い順に見る)
  • どちらに割り当ててもよい時間を割り当てる
  • 残りをしかたなく追加コストを払って★から割り当てる
  • という方針でいいだろう
  • ここまではわりとすぐ
  • 隙間を列挙する、最低交換回数を求めるあたりで大ハマリ。
  • sample 実行したら、日の最初と最後が違う人なら交換回数に含めなきゃいけないのに気づく
  • →隙間列挙が場合分けコードでどんどん汚くなりバグりまくって焦る
  • 残り30分であきらめて C に移動
  • small は、掛け算だし最小のやつに fill してけばよかろう→これ以下ならfillするという値で二分探索→ 一発OK
  • 残り15分で戻ってくるがバグバグでだめ
  • (あとで)
  • すっきり再実装、終了15分後にB small+large AC
  • 実装ゲーは相当落ち着いてやらないといかん。
  • 🤔🤔
// 予定
struct Entry {
	ll s, e, type;
};

int main() {
	int test_cases;
	cin>>test_cases;
	ll A,B, s, e;
	REP(ttt, test_cases) {
		cin>>A>>B;
		ll ra = 720, rb = 720; // のこり割当時間
		ll fr = 1440; // どっちに割り当ててもノーペナな枠
		vector<Entry> es;
		REP(i, A) {
			cin>>s>>e;
			ra -= e-s;
			fr -= e-s;
			es.push_back(Entry{s, e, 1});
		}
		REP(i, B) {
			cin>>s>>e;
			rb -= e-s;
			fr -= e-s;
			es.push_back(Entry{s, e, -1});
		}
		VI pa, pb; // dur
		ll N = 1440;
		sort(ALL(es), [](const Entry& a, const Entry& b){return a.s<b.s;});

		ll ans = 0;
		ll M = es.size();
		REP(i, M) {
			ll ni = (i+1)%M;
			if(es[i].e%N != es[ni].s%N) {
				// すきまはっけん
				if(es[i].type == es[ni].type) {
					ll dur = (es[ni].s-es[i].e+N)%N;
					if(es[i].type==1) pa.PB(dur);
					if(es[i].type==-1) pb.PB(dur);
				}
			}
			if(es[i].type!=es[ni].type) ans++;
		}
		
		sort(ALL(pa));
		sort(ALL(pb));

		REP(i, pa.size()) fr -= pa[i];
		REP(i, pb.size()) fr -= pb[i];

		// ペナルティ回避
		REP(i, pa.size()) {
			ll use = min(ra, pa[i]);
			ra -= use;
			pa[i]-=use;
		}
		REP(i, pb.size()) {
			ll use = min(rb, pb[i]);
			rb -= use;
			pb[i]-=use;
		}

		// ノーペナ消費
		{
			ll use = min(ra, fr);
			ra -= use;
			fr -= use;
		}
		{
			ll use = min(rb, fr);
			rb -= use;
			fr -= use;
		}

		// ペナルティ
		REP(i, pb.size()) {
			ll use = min(ra, pb[i]);
			if(use) ans+=2;
			ra -= use;
		}
		REP(i, pa.size()) {
			ll use = min(rb, pa[i]);
			if(use) ans+=2;
			rb -= use;
		}
		assert(ra==0);
		assert(rb==0);

		cout<<"Case #"<<ttt+1<<": "<<ans<<endl;
	}
	return 0;
}

2017-04-18

SRM 712 300 LR

22:28 |  SRM 712 300 LR - kojingharangの日記 を含むブックマーク はてなブックマーク -  SRM 712 300 LR - kojingharangの日記  SRM 712 300 LR - kojingharangの日記 のブックマークコメント

  • 長さ N の数列 s, t が与えられる。最初と最後はつながっている。
  • 操作 L: 左の要素と現在の要素を足したものを新しい要素の値とする
  • 操作 R: 右の要素と現在の要素を足したものを新しい要素の値とする
  • 操作 L, R を最大 100 回行って s が t になるならその手順、ならなければ "No solution" を返せ。
  • 2≦N≦50, 0≦要素≦10^15
  • 1 0 0 0 から始めるとどうなるか? → 1 1 -> 1 2 1 -> 1 3 3 1 -> 二項係数になる。→特に発展せず
  • 探索してくと意外とすぐ狭まるやつ? →どうもそうでもない
  • 逆から考えると何か分かるか? →わからず
  • 数列の合計は L, R どちらをやっても 2 倍に増える
  • なので 10^15≒2^50 ということで解があるなら 50 回くらいやれば十分。
  • う〜ん
  • (しばらくして)
  • 紙上で A B C D E を LL LR RL RR してどうなるか見てみると LR RL が同じになりそう
  • 実験くん
  • 全部並びとしては同じで、適用結果はLL...LLL の結果を R の数だけ左回転したものになる
  • (気づくのが遅い)
  • Σt = 2^M*Σs のとき、s に L を M 回適用した後の並びを M 回以下の R 回左シフトしたものが t と一致したら R 個の 'R' と M-R 個の 'L' をつなげたやつが答え。
  • Challengeされる
  • 🤔
  • Σs==0 のとき無条件に "No solution" にしていたorz orz
  • Σs==0 でも s==t なら "" を返すんだった。
  • せっかく思いついたのにもったいない.....
  • 🤔
  • Accepted in practice
VI rot(const VI& a, char d) {
	int lr = d=='L' ? -1 : 1;
	int N = a.size();
	VI rv(N);
	REP(i, N) rv[i] = a[(i+lr+N)%N]+a[i];
	return rv;
}

VI rotN(const VI& a, string d) {
	VI rv(a);
	for(auto c : d) rv = rot(rv, c);
	return rv;
}

VI shiftL(const VI& a) {
	int N = a.size();
	VI rv(N);
	REP(i, N) rv[i] = a[(i+1)%N];
	return rv;
}

class LR {
	public:
	string construct(vector<long long> s, vector<long long> t) {
		ll ss = accumulate(ALL(s), 0LL);
		ll st = accumulate(ALL(t), 0LL);
		if(ss>st) return "No solution";
		if(ss==st) {
			return s==t ? "" : "No solution";
		}
		if(ss==0) return "No solution";
		int cnt = 0;
		{
			bool ok = false;
			while(ss<=st) {
				if(ss==st) {
					ok=true;
					break;
				}
				ss*=2;
				cnt++;
			}
			if(!ok) return "No solution";
		}
		VI w(s);
		REP(i, cnt) {
			w = rot(w, 'L');
		}
		bool ok = false;
		int R = 0;
		REP(i, s.size()) {
			if(w==t) {
				ok = true;
				break;
			}
			w = shiftL(w);
			R++;
		}
		if(ok && R<=cnt) {
			string ans(cnt, 'L');
			REP(i, R) ans[i] = 'R';
			assert(t==rotN(s, ans));
			return ans;
		}
		return "No solution";
	}
};

2017-04-09

Google code jam 2017 Qualification D. Fashion Show

14:48 |  Google code jam 2017 Qualification D. Fashion Show  - kojingharangの日記 を含むブックマーク はてなブックマーク -  Google code jam 2017 Qualification D. Fashion Show  - kojingharangの日記  Google code jam 2017 Qualification D. Fashion Show  - kojingharangの日記 のブックマークコメント

  • NxN grid の各 cell に . + x o が置いてある。. を + or x or o に、+ or x を o に書き換えることができる。
  • スコアを . は 0, +, x は 1, o は 2 とする。合計スコアの最大値とそのときの配置を出力せよ。
  • 縦横の並びそれぞれについて +xo が 2 つあるときは1つは必ず + でないといけない
  • 斜めの並びそれぞれについて +xo が 2 つあるときは1つは必ず x でないといけない
  • 1≦N≦100
  • 全然わからんので実験, 適当なgreedyのやつでsmallをやってみる→手かがりなし :(
  • (しばらくして)
  • しかたないので線形計画問題に落とし込む。各cellに .+xo に対応する4つの変数 d p t o を割り当てて
  • 1cell にはどれかしか置けない ... d+p+t+o≦1
  • もともと + が置いてあったら + or o しか置けない ... -p-o≦-1
  • もともと x が置いてあったら x or o しか置けない ... -t-o≦-1
  • もともと o が置いてあったら o しか置けない ... -o≦-1
  • 縦横それぞれ, x or o は 1 個までしか置けない ... 縦横それぞれ, 集合に入る変数について t+o≦1
  • 斜めの線(/\)それぞれ, + or o は 1 個までしか置けない ... 斜めの線それぞれ, 集合に入る変数について p+o≦1
  • という条件のもと、Σ0d+1p+1t+2oを最大化する
  • 以上を行列にして simplex 法で解いて解の復元
  • N=20 くらいまでならさくさく求まる, N=30 (3600変数, 1078制約式) はだめ
  • まったく解けないよりはましだが、う〜む
  • 🤔
// simplex 法バージョン
// 新たな盤面を計算する部分だけ抜粋
vector<string> solve(const vector<string>& w) {
	int N = w.size();
	int pre = 0;
	REP(y, N) REP(x, N) if(w[y][x]!='.') pre++;

	int vars = 4*N*N;
	int cs = N*N + pre + 2*N + 2*(2*N-1);
	Mat m(cs, Vec(vars));
	Vec b(cs);
	Vec c(vars);
	int base = 0;
	{
		// .+xo
		int score[4] = {0, 1, 1, 2};
		REP(i, N*N) {
			REP(j, 4) m[base][4*i+j] = 1;
			b[base] = 1;
			REP(j, 4) c[4*base+j] = score[j];
			base++;
		}
	}
	// pre
	{
		REP(y, N) REP(x, N) {
			int idx = y*N+x;
			if(w[y][x]=='+') {
				// must be + or o
				m[base][idx*4+1] = -1;
				m[base][idx*4+3] = -1;
				b[base] = -1;
				base++;
			}
			if(w[y][x]=='x') {
				// must be x or o
				m[base][idx*4+2] = -1;
				m[base][idx*4+3] = -1;
				b[base] = -1;
				base++;
			}
			if(w[y][x]=='o') {
				// must be o
				m[base][idx*4+3] = -1;
				b[base] = -1;
				base++;
			}
		}
	}
	// たて Σxo <= 1
	{
		REP(x, N) {
			REP(y, N) {
				int idx = y*N+x;
				m[base][idx*4+2] = 1;
				m[base][idx*4+3] = 1;
			}
			b[base] = 1;
			base++;
		}
	}
	// よこ Σxo <= 1
	{
		REP(y, N) {
			REP(x, N) {
				int idx = y*N+x;
				m[base][idx*4+2] = 1;
				m[base][idx*4+3] = 1;
			}
			b[base] = 1;
			base++;
		}
	}
	// ななめ Σ+o <= 1
	{
		REP(k, 2*N-1) {
			REP(y, N) {
				REP(x, N) {
					if(x+y==k) {
						int idx = y*N+x;
						m[base][idx*4+1] = 1;
						m[base][idx*4+3] = 1;
					}
				}
			}
			b[base] = 1;
			base++;

			REP(y, N) {
				REP(x, N) {
					if((N-1-x)+y==k) {
						int idx = y*N+x;
						m[base][idx*4+1] = 1;
						m[base][idx*4+3] = 1;
					}
				}
			}
			b[base] = 1;
			base++;
		}
	}
	auto p = simplex(m, b, c);
	auto vs = p.second;
	auto rv = w;
	REP(y, N) REP(x, N) {
		rv[y][x] = '.';
		ll sum = 0;
		REP(i, 4) sum += (ll)vs[(y*N+x)*4+i];
		assert(sum==1);
		if(vs[(y*N+x)*4+0]) rv[y][x] = '.';
		if(vs[(y*N+x)*4+1]) rv[y][x] = '+';
		if(vs[(y*N+x)*4+2]) rv[y][x] = 'x';
		if(vs[(y*N+x)*4+3]) rv[y][x] = 'o';
	}
	return rv;
}
  • (あとで)
  • o は + と x が両方置いてあることにすると +, x それぞれ独立に最適に置いた時の解と一致して辻褄が合う、らしい
  • ほ〜〜〜〜〜
  • 思いつく人すごーい
  • x は縦と横の依存関係がきれいなのでgreedyで (正確に説明できなくてもやもやする)
  • + は\と/を2部グラフにして最大マッチングさせる
  • 書いたことないのでハマる。
  • Accepted in practice
// 新たな盤面を計算する部分だけ抜粋
vector<string> solve2(const vector<string>& w) {
	int N = w.size();
	vector<string> xs(N, string(N, '.'));
	vector<string> ps(N, string(N, '.'));
	vector<string> rv(N, string(N, '.'));

	// put x
	{
		REP(y, N) REP(x, N) if(w[y][x]=='x' || w[y][x]=='o') xs[y][x] = 'x';
		vector<bool> Ys(N), Xs(N);
		REP(y, N) REP(x, N) if(xs[y][x]=='x') Ys[y]=Xs[x]=true;
		while(1) {
			REP(y, N) REP(x, N) if(xs[y][x]=='.' && !Xs[x] && !Ys[y]) {
				xs[y][x] = 'x';
				Ys[y]=Xs[x]=true;
			}
			if(count(ALL(Xs), true)==N && count(ALL(Xs), true)==N) break;
		}
	}
	// put +
	{
		REP(y, N) REP(x, N) if(w[y][x]=='+' || w[y][x]=='o') ps[y][x] = '+';
		bipartite_matching bm((2*N-1)*2);
		VVI edges(2*N-1, VI(2*N-1));
		vector<bool> usedD0(2*N-1), usedD1(2*N-1);
		REP(y, N) REP(x, N) if(ps[y][x]=='+') {
			int d0 = x+y;
			int d1 = N-1-x+y;
			usedD0[d0] = 1;
			usedD1[d1] = 1;
		}
		REP(y, N) REP(x, N) {
			int d0 = x+y;
			int d1 = N-1-x+y;
			if(!usedD0[d0] && !usedD1[d1]) {
				edges[d0][d1] = 1;
			}
		}
		REP(d0, 2*N-1) REP(d1, 2*N-1) {
			if(edges[d0][d1]) bm.add_edge(d0, 2*N-1+d1);
		}
		int matched=bm.run();
		REP(i, 2*N-1) if(bm.match[i]!=-1) {
			// x+y
			int d0 = i;
			// (N-1-x)+y
			int d1 = bm.match[i]-(2*N-1);
			// d0+d1 = N-1+2y
			int y = (d0+d1-N+1)/2;
			int x = d0-y;
			ps[y][x] = '+';
		}
	}
	REP(y, N) REP(x, N) {
		rv[y][x] = xs[y][x];
		if(ps[y][x]=='+') rv[y][x] = rv[y][x]=='x' ? 'o' : '+';
	}
	return rv;
}

2017-04-02

2017 TCO Round1A 1000 PolygonRotation

16:27 |  2017 TCO Round1A 1000 PolygonRotation - kojingharangの日記 を含むブックマーク はてなブックマーク -  2017 TCO Round1A 1000 PolygonRotation - kojingharangの日記  2017 TCO Round1A 1000 PolygonRotation - kojingharangの日記 のブックマークコメント

  • 2次元平面上の凸ポリゴンが整数点座標 x[i], y[i] N 個で与えられる。ポリゴンをy軸中心に回転させたときにできる立体の体積を計算せよ。
  • Y軸上の点は2点与えられる。(0, Ymin), (0, Ymax)
  • 3≦N≦50, -100≦x[i]≦100, -100≦Ymin≦y[i]≦Ymax≦100
  • 同じ場所に2点はない。
  • 時計回り順に与えられる。
  • どの3点も同じ直線上にない。
  • 残り28分くらい
  • んーややこしそうだ
  • 適当なyで切りまくって円錐台の体積の和にするんだろう
  • 2線分の交点の式を検索。
  • まずはy軸に対して左側と右側の線分集合に分ける必要があろう (めんどくさい)
  • おわり

あとで

  • 回転体の断面の d|x|/dy が変化する y 座標群は与えられた y 座標群 ∪ 左側をx=0で反転させた線分集合と右側の線分集合の交点
  • y 座標群を y でソートした上で、それぞれの y 座標に対応する |x| = max(左側のx, 右側のx) を求める
  • y[i], y[i+1] に対応する円錐台の体積の和が答え
  • 落ち着いて冷静にシンプルに書いてみるとできるが本番ではなかなか時間が足りないのう。
  • Accepted in practice
class PolygonRotation {
	public:
	double getVolume(vector <int> x, vector <int> y) {
		int N = x.size();
		int mid;
		REP(i, N) if(x[i]==0) mid=i;
		vector<P> l, r, all;
		REP(i, N) {
			if(i<=mid) {
				r.PB(P{(double)x[i], (double)y[i]});
			}
			if(i>=mid) {
				l.PB(P{(double)-x[i], (double)y[i]});
			}
		}
		l.PB(P{(double)-x[0], (double)y[0]});
		vector<double> ys(ALL(y));
		REP(i, l.size()-1) REP(j, r.size()-1) {
			auto rv = intersection(l[i], l[i+1], r[j], r[j+1]);
			if(rv.first) {
				ys.PB(rv.second.y);
			}
		}
		sort(ALL(ys));
		vector<double> xs(ys.size());
		REP(i, ys.size()) {
			// find max x for ys[i]
			double y = ys[i];
			for(auto& e : {l, r}) {
				REP(j, e.size()-1) {
					double ra = (y - e[j].y) / (e[j+1].y-e[j].y);
					if(0 <= ra && ra <= 1) {
						xs[i] = max(xs[i], e[j].x + ra * (e[j+1].x-e[j].x));
					}
				}
			}
		}
		double ans = 0;
		REP(i, ys.size()-1) {
			double h = ys[i+1]-ys[i];
			double r1 = xs[i];
			double r2 = xs[i+1];
			ans += M_PI*h/3.0*(r1*r1+r1*r2+r2*r2);
		}
		return ans;
	}
};

2017-02-12

SRM 708 Div1 500 PalindromicSubseq

13:01 |  SRM 708 Div1 500 PalindromicSubseq - kojingharangの日記 を含むブックマーク はてなブックマーク -  SRM 708 Div1 500 PalindromicSubseq - kojingharangの日記  SRM 708 Div1 500 PalindromicSubseq - kojingharangの日記 のブックマークコメント

  • 久しぶりの参加。
  • 長さ 1≦N≦3000 の文字列 s が与えられる。
  • X[i] = "s[i] を含む s の部分列のうちそれが回文になるものの個数 mod 10^9+7" とする
  • i in [0, N) に対して (i+1)*X[i] の XOR を求めよ。
  • ひょ〜
  • サンプルを見て初めて問題の意味が分かる
  • SRM 中に出たアイデアや考察
    • 回文ということは中央に軸があるので軸の位置を全部試す感じでいけないか?
    • 軸の左右にどちらもある文字しか使われない
    • L, R より外で回文のとき、内側は何通り?
  • さっぱり
  • おわり
  • あとで
  • 上位陣のをいくつか見る
  • s[i] を必ず採用する時、部分列が回文になるなら s[i] の反対側に対応する同じ文字があるはずである。(s[i] が中央なら s[i] 自身)
  • なるほど

  • s[i] が決まればそれに対応する文字の位置は N 種類
  • s[i], 対応する文字を左から L, R とすると ? ? ? L * * * R ? ? ? という形で表せる
  • 内側(*)だけで回文になる部分列の個数と外側(?)だけで回文になる部分列の個数を N^2 DP で求めておけば
  • L, R が決まったときの組み合わせ総数は 内側回文個数 * 外側回文個数 O(1) で求まる
  • X[i] を計算するときは L, R の組み合わせは N 通りなので X[i] は O(N) で求まる
  • 全体 O(N^2)
  • 内側 : dp1[i][j] ... [i, j] 内の部分列かつ回文の個数
  • 外側 : dp2[i][j] ... [0, i), (j, N) 内の部分列かつ回文の個数
  • として書いてみたが内外共に半開区間の方がいろいろすっきりするのかもしれない。
  • DP2つとそれを組み合わせる処理を正確に書かないといけないので難しい.....
  • Accepted in practice
class PalindromicSubseq {
	public:
	int solve(string s) {
		ll N = s.size();
		// dp1[i][j] ... [i, j] 内での回文の個数
		vector<vector<modll>> dp1(N, vector<modll>(N));
		// dp2[i][j] ... [0, i), (j, N) 内での回文の個数
		vector<vector<modll>> dp2(N, vector<modll>(N));
		RANGE(L, 0, N+1) REP(i, N-L+1) {
			int j = i+L-1;
			if(L==0) {
				if(0<=j && i<N) dp1[i][j] = 1; // empty
			} else {
				modll v = 0;
				// s[i] を採用しない場合 (s[j]は採用するしないどちらも含む)
				if(i+1<N) v += dp1[i+1][j];
				// s[j] を採用しない場合 (s[i]は採用するしないどちらも含む)
				if(0<=j-1) v += dp1[i][j-1];
				// s[i], s[j] 両方採用しない場合が 2 回数えられている分を減らす
				if(0<=j-1 && i+1<N) v -= dp1[i+1][j-1];
				if(s[i]==s[j]) {
					// s[i], s[j] を採用できる。採用するとその内側の組み合わせ総数だけ増える
					if(0<=j-1 && i+1<N) v += dp1[i+1][j-1];
				}
				dp1[i][j] = v;
			}
		}
		for(int L=N;L>=1;L--) REP(i, N-L+1) {
			int j = i+L-1;
			if(L==N) {
				dp2[i][j] = 1; // empty
			} else {
				modll v = 0;
				// 同様
				if(0<=i-1) v += dp2[i-1][j];
				if(j+1<N) v += dp2[i][j+1];
				if(0<=i-1 && j+1<N) {
					v -= dp2[i-1][j+1];
					if(s[i-1]==s[j+1]) {
						// s[i-1], s[j+1] を採用できる。採用するとその外側の組み合わせ総数だけ増える
						v += dp2[i-1][j+1];
					}
				}
				dp2[i][j] = v;
			}
		}
		ll ans = 0;
		REP(i, N) {
			modll X = 0;
			REP(j, N) {
				if(s[i]==s[j]) {
					ll I=i, J=j;
					if(I>J) swap(I, J);
					// ここでは I, J がどちらも採用され I, J が回文にて対応関係にある。
					// 内側は [I+1, J-1] 内の回文の個数
					// 外側は [0, I), (J, N) 内の回文の個数
					// これらの積が "I, J がどちらも採用され回文にて I, J が対応関係にある" 場合の組み合わせ総数。
					X += (I+1<=J-1 ? dp1[I+1][J-1] : modll(1)) * dp2[I][J];
				}
			}
			ans ^= modll(i+1LL)*X;
		}
		return ans;
	}
};
|