Hatena::Grouptopcoder

kojingharangの日記 RSSフィード

|

2012-10-31

SRM 559 Div1 250 HyperKnight

12:57 |  SRM 559 Div1 250 HyperKnight - kojingharangの日記 を含むブックマーク はてなブックマーク -  SRM 559 Div1 250 HyperKnight - kojingharangの日記  SRM 559 Div1 250 HyperKnight - kojingharangの日記 のブックマークコメント

  • チェスのナイトの動きを一般化して (±A, ±B), (±B, ±A) の 8 通りに移動できるとする
  • 盤面 NR x NC があるとして、1回の移動後も盤面にいるような選択肢の数が K 個であるような出発点の数を求める。
  • K in [0, 8]. NR,NC,A,B in [1, 10**9]
  • 座標圧縮みたいにして 5x5 マスで考える
  • 5x5 のサブエリア[x0, x1] x [y0, y1] (x0, x1, y0, y1 in [0, 4], x0 < x1, y0 < y1)のうち中心が含まれるものを全列挙して、
  • 駒が K 個だったら実際の盤面でそのサブエリアに対応する置き方が何通りあるか数えて足す
/*
□駒□駒□
駒□□□駒
□□×□□
駒□□□駒
□駒□駒□

K=2 になるようなサブエリア例
□■■■□
□■■■□
□■■■□
□□□□□
□□□□□

K=2 になるようなサブエリア例
□□■■■
□□■■■
□□■■■
□□□□□
□□□□□

K=2 になるようなサブエリア例
□□■■□
□□■■□
□□■■□
□□■■□
□□■■□

*/
  • 置き方はx軸y軸に分けて考える
  • ごりごり書いてたら Sample4 以外通ったけどそこ直したら Sample1 が通らなくなってとか繰り返してるうちに終了

(あとで)

  • 圧縮してた座標と範囲を戻す
  • たとえばサブエリアが占める範囲の最小値は「左部分の幅の最小値 + 1(中央) + 右部分の幅の最小値」
  • (x0, x1) が (0, 4) のとき(両端で終わってるとき)はどこに置いてもいいしサブエリアの幅は1通りしかないので「幅 - サブエリアの幅 + 1」通り
  • そうじゃないときは左右どちらかまたは両方が壁に接していないといけないので置く場所は決まってて、有効なサブエリアの幅の範囲数通りになる
  • サブエリアの幅が盤面に入るかとかチェックもする
  • (何言ってるかわからなくなってきた)

↓あとで (accepted in practice)

class HyperKnight {
	public:
	long long countCells(int A, int B, int NR, int NC, int K) {
		VVI pat(5, VI(5, 0));
		int eX[] = {0,0,1,1,3,3,4,4};
		int eY[] = {1,3,0,4,0,4,1,3};
		REP(i, 8) pat[eY[i]][eX[i]]=1;
		//cout<<pat<<endl;
		if(B>A) swap(A, B);
		
		ll ans = 0;
		REP(y0, 5) {
			REP(x0, 5) {
				RANGE(y1, y0, 5) {
					RANGE(x1, x0, 5) {
						if(x1<2||2<x0||y1<2||2<y0) continue;
						int co=0;
						RANGE(y, y0, y1+1) RANGE(x, x0, x1+1) co+=pat[y][x];
						//cout<<x0<<" "<<y0<<"   "<<x1<<" "<<y1<<" ++ "<<co<<endl;
						if(co==K) {
							//cout<<x0<<" "<<y0<<"   "<<x1<<" "<<y1<<" --- "<<endl;
							int tb[] = {0, B, A, A+1};
							
							int a0=x0, a1=x1, N=NC;
							ll cc[2];
							int ok=1;
							REP(loop, 2) {
								int i0 = abs(a0-2);
								int i1 = abs(a1-2);
								ll cmin0 = tb[i0];
								ll cmin1 = tb[i1];
								ll cmax0 = tb[i0+1]-1;
								ll cmax1 = tb[i1+1]-1;
								// ながさ in [c0, c1]
								ll c0 = cmin0 + 1 + cmin1;
								ll c1 = cmax0 + 1 + cmax1;
								if(!(i0==2||i1==2)) {
									if(!(c0<=N&&N<=c1)) ok=0;
								}
								if(c0>N) ok=0;
								c1 = min((ll)N, c1);
								//cout<<c0<<" "<<c1<<endl;
								ll c = 0;
								if(i0==2&&i1==2) c = N-c0+1;
								else c = c1-c0+1;
								a0=y0, a1=y1, N=NR;
								cc[loop] = c;
							}
							
							if(ok) {
								ans += cc[0] * cc[1];
								//cout<<x0<<" "<<y0<<"   "<<x1<<" "<<y1<<" --- "<<cc[0]<<" "<<cc[1]<<endl;
							}
						}
					}
				}
			}
		}
		return ans;
	}
};

2012-10-11

SRM 557 Div1 250 FoxAndMountainEasy

19:49 |  SRM 557 Div1 250 FoxAndMountainEasy - kojingharangの日記 を含むブックマーク はてなブックマーク -  SRM 557 Div1 250 FoxAndMountainEasy - kojingharangの日記  SRM 557 Div1 250 FoxAndMountainEasy - kojingharangの日記 のブックマークコメント

  • 1歩上か下に N 回移動する。最初の高さは H0 で最後の高さは HN であることがわかっている。途中、高さは0未満にはならない。
  • 'U' or 'D' からなる文字列 S が与えられるので途中の上下動のパターンとしてありうるかどうかを答える。
  • N≦100000, |S|≦50
  • H0, HN の高さの差が N 回より大きい時は無理
  • 全部上 or 全部下にいって余った分は上下の組になんないといけないので N-|H0-HN| は 2 で割り切れないとだめ
  • H0, HN から上下それぞれの回数 U, D が決まる。H0<HN なら上が HN-H0 回、に加えて上下の組が N-|H0-HN| コ。逆も同様。
  • 文字列内の上下の回数 u, d が U, D に収まってないと無理
  • 一時的にでも高さが 0 未満になっちゃだめなので、文字列内で一番下がる位置を覚えておいて
  • 文字列の先頭としてありうる一番高い地点から始めて 0 未満になるならだめ
  • だめじゃなければOK
  • が!一番高いとこは H0 か HN の高い方だろうと早とちりして failed system test.
  • 一番高くするには文字列の左にできるだけ上を置いて文字列の後にできるだけ下を置けばいいので
  • 一番高いとこは H0 + U - u

↓あとで (accepted in practice)

class FoxAndMountainEasy {
	public:
	string possible(int n, int h0, int hn, string HI) {
		if(abs(h0-hn)>n) return "NO";
		if( (n-abs(h0-hn))%2 != 0 ) return "NO";
		int U=0, D=0, UD=0;
		if(h0<hn) U=abs(h0-hn);
		else      D=abs(h0-hn);
		UD = n-abs(h0-hn);
		U += UD/2;
		D += UD/2;
		int u=count(ALL(HI), 'U');
		int d=count(ALL(HI), 'D');
		if(U<u || D<d) return "NO";
		int Min=0;
		int pos=0;
		FOR(e, HI) {
			if(*e=='D') pos--;
			if(*e=='U') pos++;
			Min = min(Min, pos);
		}
		//if(abs(Min) > max(h0, hn)) return "NO";     // だめ
		if(abs(Min) > h0+U-u) return "NO";            // OK
		return "YES";
	}
};

2012-09-20

Codeforces #139 Div2 C - Barcode

22:32 |  Codeforces #139 Div2 C - Barcode - kojingharangの日記 を含むブックマーク はてなブックマーク -  Codeforces #139 Div2 C - Barcode - kojingharangの日記  Codeforces #139 Div2 C - Barcode - kojingharangの日記 のブックマークコメント

  • # か . が書いてあるグリッドがある。これを同じ色の幅が X~Y なバーコードにするために書き換える必要のあるマスの個数の最小値を求める。
  • W,H,X,Y ≦ 1000
  • DP筋を鍛えるシリーズ

(あとで)

  • dp[col][x] : x 列含めて左側の全セルが無矛盾であるように x 列を色 col(0or1) で塗った時の書き換えセル数の最小値
  • とすると
  • X~Y なる i について、今みてる列から i 列前は(もしあれば)今と違う色で、その次 x-i+1 列から今の列は全部今の色
  • じゃないといけないので
  • dp[col][x] = min(dp[1-col][x-i] + (Σj列を全部colに塗るときの書き換え回数 for j in [x-i+1, x]) for i in [X, Y])
  • ただし最初のしましまの太さも X~Y じゃなきゃいけないので x-i+1≧0 のときだけ更新する必要がある
  • min(dp[0][W-1], dp[1][W-1]) が答え
  • X=2, Y=4 の場合
  • ?■□□□今
  • ??■□□今
  • ???■□今

↓あとで (accepted in practice)

#define INF (1<<30)

int dp[2][1010];

int main() {
	ll H,W,X,Y;
	while(cin>>H>>W>>X>>Y) {
		REP(col, 2) REP(x, 1010) dp[col][x]=INF;
		
		VVI co(2, VI(W));
		REP(i, H) {
			string s;
			cin>>s;
			REP(x, s.size()) co[0][x]+=s[x]=='.', co[1][x]+=s[x]!='.';
		}
		REP(x, W) {
			REP(col, 2) {
				RANGE(i, X, Y+1) {
					int v = 0;
					if(x-i>=0) v += dp[1-col][x-i];
					if(x-i+1>=0) {
						RANGE(j, x-i+1, x+1) v += co[col][j];
						dp[col][x] = min(dp[col][x], v);
					}
				}
			}
		}
		
		cout<<min(dp[0][W-1], dp[1][W-1])<<endl;
	}
	
	return 0;
}

2012-09-17

Codeforces Div2 C - Bracket Sequence

00:37 |  Codeforces Div2 C - Bracket Sequence - kojingharangの日記 を含むブックマーク はてなブックマーク -  Codeforces Div2 C - Bracket Sequence - kojingharangの日記  Codeforces Div2 C - Bracket Sequence - kojingharangの日記 のブックマークコメント

  • [ ] ( ) からなる文字列 S が与えられる。
  • カッコの対応がとれている部分文字列のうち [ の個数が最も多いものについてその数と部分文字列を出力する
  • |S| ≦ 100000
  • これはスタックでしょう
  • できそうでできない
  • ..........
  • おわり

(あとで)

  • 今回はロシアの grey_wind さんの解答を参考に。
  • カッコの対応がとれている範囲 [NL, NR] をどうにかして探してその範囲内の [ の数を数えて最大のものを出力する。
  • [NL, NR] の探し方は
  • 文字列を先頭から見ていって、開きカッコなら文字とその出現位置を push
  • 閉じカッコなら top が対応する開きカッコかどうか調べる
  • 対応してない場合、現時点で開いているカッコはすべて対応しないことになるのでスタックをクリア
  • 対応してる場合、開きカッコの出現位置から現在位置までがカッコに対応したことになる
  • ところがこの問題の場合、「NL=今閉じたカッコに対応する開きカッコの位置」ではなく「NL=兄弟の先頭」にしないといけない(例: [ ] [ ] [ ] )
  • 兄弟の先頭は親の開きカッコの次の文字 or 親カッコがない場合は矛盾してない最初の文字 (start)
  • 範囲内の [ は累積和を計算しておくと O(1) で求まる。
  • うーむ賢い..

↓実装練習 (accepted in practice)

int cum[100010]; // i is # of '[' in S[k] for k in [0, i)

int count(int L, int R) {
	return cum[R+1]-cum[L];
}

int main() {
	//ios::sync_with_stdio(false);
	string S;
	while(cin>>S) {
		cum[0] = 0;
		REP(i, S.size()) {
			cum[i+1] = cum[i] + (S[i]=='[');
		}
		deque<pair<char, int> > mo;
		int N=S.size();
		int L=-1, R=-1;
		int co = 0;
		int start = 0;
		REP(i, N) {
			char op = S[i]==']' ? '[' : '(';
			if(S[i]=='[' || S[i]=='(') {
				mo.PB(MP(S[i], i));
			} else {
				if(mo.size() && mo.back().first==op) {
					mo.pop_back();
					int NL = start;
					if(mo.size()) NL = mo.back().second + 1;
					int NR = i;
					int nco = count(NL, NR);
					if( co < nco ) {
						co = nco;
						L=NL;
						R=NR;
					}
				} else {
					mo.clear();
					start = i+1;
				}
			}
		}
		cout<<co<<endl;
		if(co>0) cout<<S.substr(L, R-L+1)<<endl;
	}
	
	return 0;
}

2012-09-07

SRM 555 Div1 555 XorBoard

00:34 |  SRM 555 Div1 555 XorBoard - kojingharangの日記 を含むブックマーク はてなブックマーク -  SRM 555 Div1 555 XorBoard - kojingharangの日記  SRM 555 Div1 555 XorBoard - kojingharangの日記 のブックマークコメント

  • 0 or 1 が入る W x H のグリッドに 0 が書いてある。行と列すべての 0 1 を反転する回数がそれぞれ与えられる。
  • 反転後 1 の数が S になるような反転のさせかたは何通りあるか。
  • 各行/各列の反転回数どれかが違えば違うとする。
  • W, H ≦ 1555
  • (しばらくして)
  • 行のうち反転回数が奇数になるものを ro, 同じく列のを co とすると
  • 反転後の 1 の数は W*ro + H*co - 2*ro*co となる
  • こんな ro, co が何通りあるか数えればよさげ
  • ro を分解して H 個に振り分ける?分割数?
  • ..........
  • 奇数の場所は combination(H, ro) 通りあるな
  • そこから進まない
  • ..........
  • おわり

(あとで)

  • 行の場合
  • 奇数の場所は combination(H, ro) 通りあって、それぞれについて
  • 奇数のところから 1 を取ると→全部偶数になる
  • 偶数ということは2コのペア (RC-ro)/2 を H 個に振り分ければいい (なるへそ)
  • これは combination((RC-ro)/2 + H-1, H-1) 通り
  • 反転カード((RC-ro)/2 コ)または区切り棒(H-1)が置ける (RC-ro)/2 + H-1 個の置き場から H-1 個選んで区切り棒を置く。
  • 列も同様。行と列は独立なのでかければいい
  • combination の表は combination(N, M) == combination(N-1, M-1) + combination(N-1, M) を使って DP


↓実装練習 (passed system test in practice room)

#define MOD 555555555

int comb[2500][2500];

class XorBoard {
	public:
	int count(int H, int W, int RC, int CC, int S) {
		// nCm = n-1Cm-1 + n-1Cm
		REP(n, 2500) comb[n][0] = 1;
		RANGE(n, 1, 2500) RANGE(m, 1, 2500) comb[n][m] = ((ll)comb[n-1][m-1] + comb[n-1][m]) % MOD;
		
		ll ans = 0;
		REP(ro, H+1) if(ro<=RC && (RC-ro)%2==0)
		REP(co, W+1) if(co<=CC && (CC-co)%2==0)
		if(W*ro+H*co-2*ro*co==S) {
			ll lans = (ll)comb[H][ro] * comb[W][co] % MOD * comb[(RC-ro)/2 + H-1][H-1] % MOD * comb[(CC-co)/2 + W-1][W-1] % MOD;
			ans = (ans + lans) % MOD;
		}
		return ans;
	}
};
|