Hatena::Grouptopcoder

kojingharangの日記 RSSフィード

|

2013-12-23

SRM 601 Div1 500 WinterAndSnowmen

16:54 |  SRM 601 Div1 500 WinterAndSnowmen - kojingharangの日記 を含むブックマーク はてなブックマーク -  SRM 601 Div1 500 WinterAndSnowmen - kojingharangの日記  SRM 601 Div1 500 WinterAndSnowmen - kojingharangの日記 のブックマークコメント

  • N, M が与えられる。整数 1〜N の部分集合 A と 1〜M の部分集合 B を互いに重複なしで選んだとき、A の全要素の XOR < B の全要素の XOR となる選び方は何通りあるか。mod 1,000,000,007 で求める。
  • 1≦N,M≦2000
  • 1〜2000 の部分集合の全要素の XOR が 0 以上 2048 未満なのがポイントっぽい
// dp[I%2][XA][XB] ... [0, min(N,I)) の部分集合の全要素の XOR が XA, [0, min(M,I)) の部分集合の全要素の XOR が XB のときの選び方の数
int dp[2][2048][2048];
としてみると

初期値は dp[0][0][0] = 1
更新は
i in [1, max(N, M)], xa,xb in [0, 2048)  について
数字 i を使わない場合 → dp[i][xa][xb] = dp[i-1][xa][xb]
i≦N かつ数字 i を A に入れる場合 → dp[i][xa^i][xb] += dp[i-1][xa][xb]
i≦M かつ数字 i を B に入れる場合 → dp[i][xa][xb^i] += dp[i-1][xa][xb]
答え = Σ dp[max(N,M)][xa][xb] ただし xa<xb
  • 書いてみてサンプル通ったけどそういえば最内ループの処理回数が 2000*2048*2048 になってだめだ! (終了数分前)
  • おわり
  • 8 分で解いた Petr の解答と kinaba さんのツイートを見る
  • 基本は上のを使うんだけど、あと2ステップ必要っぽい
  • というわけで
  • (1) 最初の遅いやつ
// (1)
// dp[I%2][XA][XB] ... [0, min(N,I)) の部分集合の全要素の XOR が XA, [0, min(M,I)) の部分集合の全要素の XOR が XB のときの選び方の数
int dp[2][2048][2048];
class WinterAndSnowmenSameBits {
	public:
	int getNumber(int N, int M) {
		CLEAR(dp, 0);
		int men=0;
		dp[men][0][0]=1;
		int xors = 1;
		while(xors <= max(N, M)) xors*=2;
		
		RANGE(i, 1, max(N, M)+1) {
			CLEAR(dp[men^1], 0);
			REP(xa, xors) REP(xb, xors) {
				if(dp[men][xa][xb]==0) continue;
				PLUS(dp[men^1][xa][xb], dp[men][xa][xb]);
				if(i<N+1) PLUS(dp[men^1][xa^(i)][xb], dp[men][xa][xb]);
				if(i<M+1) PLUS(dp[men^1][xa][xb^(i)], dp[men][xa][xb]);
			}
			men^=1;
		}
		int ans=0;
		REP(xa, xors) REP(xb, xors) if(xa<xb) PLUS(ans, dp[men][xa][xb]);
		return ans;
	}
};
  • (2) dp定義は変えず、全 XA<XB のペアを「上から見て same_bits ビットは同じでその次のビットで XA<XB が決まる」ような仲間にグループわけする。(same_bits in [0, 最大ビット幅) )
  • グループ分けしただけなのでまだ遅い
// (2)
// dp[I%2][XA][XB] ... [0, min(N,I)) の部分集合の全要素の XOR が XA, [0, min(M,I)) の部分集合の全要素の XOR が XB のときの選び方の数
int dp[2][2048][2048];
class WinterAndSnowmen {
	public:
	int getNumber(int N, int M) {
		int bits = 1;
		while(1<<bits <= max(N, M)) bits++;
		int xors = 1<<bits;
		
		int ans=0;
		REP(same_bits, bits) { // ここ追加
			CLEAR(dp, 0);
			int men=0;
			dp[men][0][0]=1;
			RANGE(i, 1, max(N, M)+1) {
				CLEAR(dp[men^1], 0);
				REP(xa, xors) REP(xb, xors) {
					if(dp[men][xa][xb]==0) continue;
					PLUS(dp[men^1][xa][xb], dp[men][xa][xb]);
					if(i<N+1) PLUS(dp[men^1][xa^(i)][xb], dp[men][xa][xb]);
					if(i<M+1) PLUS(dp[men^1][xa][xb^(i)], dp[men][xa][xb]);
				}
				men^=1;
			}
			// あえて一部だけ抜き出す
			REP(xa, xors) REP(xb, xors) if(((xa^xb)>>(bits-same_bits))==0 && ((xa^xb)>>(bits-same_bits-1))&1 && xa<xb) PLUS(ans, dp[men][xa][xb]);
		}
		return ans;
	}
};
  • (3) さらに dp の定義を以下のように変えると状態数が 2*2048*2*2 に減る(うおおぉー)
  • 処理回数も 11*2000*11*2*2 = 968000 以下となって間に合う
// (3)
// dp[I%2][PREFIX_XA_XOR_XB][DIFFBIT_XA][DIFFBIT_XB]
// ... [0, min(I, N)) の部分集合の xor を XA, 
//     [0, min(I, M)) の部分集合の xor を XB としたとき,
//     XA^XB の prefix(同じビットの部分) が PREFIX_XA_XOR_XB で,
//     XA の「違うビット位置」のビットが DIFFBIT_XA で, 
//     XB の「違うビット位置」のビットが DIFFBIT_XB のときの組み合わせ数
int dp[2][2048][2][2];
#define PLUS(a, b) (a) = ((ll)(a)+(b))%mod
class WinterAndSnowmen {
	public:
	int getNumber(int N, int M) {
		int bits = 1; // N,Mの最大ビット幅
		while(1<<bits <= max(N, M)) bits++;
		
		int ans=0;
		REP(same_bits, bits) {
			CLEAR(dp, 0);
			int men=0;
			dp[men][0][0][0]=1;
			RANGE(i, 1, max(N, M)+1) {
				CLEAR(dp[men^1], 0);
				REP(prefix, 1<<same_bits) REP(diffA, 2) REP(diffB, 2) {
					if(dp[men][prefix][diffA][diffB]==0) continue;
					int iPrefix = i>>(bits-same_bits);
					int iDiff = (i>>(bits-same_bits-1))&1;
					PLUS(dp[men^1][prefix][diffA][diffB], dp[men][prefix][diffA][diffB]);
					if(i<N+1) PLUS(dp[men^1][prefix^iPrefix][diffA^iDiff][diffB], dp[men][prefix][diffA][diffB]);
					if(i<M+1) PLUS(dp[men^1][prefix^iPrefix][diffA][diffB^iDiff], dp[men][prefix][diffA][diffB]);
				}
				men^=1;
			}
			PLUS(ans, dp[men][0][0][1]); // prefix の XA^XB == 0 ←→ prefixが等しい。diffA==0 && diffB==1 ←→ XA<XB
		}
		return ans;
	}
};

  • accepted in practice

2013-12-22

SRM 600 Div1 600 PalindromeMatrix

17:03 |  SRM 600 Div1 600 PalindromeMatrix - kojingharangの日記 を含むブックマーク はてなブックマーク -  SRM 600 Div1 600 PalindromeMatrix - kojingharangの日記  SRM 600 Div1 600 PalindromeMatrix - kojingharangの日記 のブックマークコメント

  • W x H のグリッドに 0 か 1 が書いてある。回文である行が R 個以上、列が C 個以上にしたい。最低何箇所書き換える必要があるか?
  • 2≦W,H≦14
  • 0≦R≦H, 0≦C≦W
  • 回文にする行/列を固定(2^28通り)→UnionFindで同じにするセルを結ぶ→各グループに含まれる数字のうち少ない方の個数だけ書き換える必要あり
  • しか思いつかない。
  • おわり
  • Editorial を見る
  • 回文にする列(14C7=3432通り)を決めて、あとはDPらしい。
int dp[16][16]; // dp[Y][r] ... y<Y or H-1-Y<y までの範囲に関して r 個以上の行と C 個以上の列が回文であるようにした時の最小コスト
  • として、更新するときは今回新たに塗るところ(y=Y, y=H-1-Y)の 2*W マスをどうするか考える。
  • 塗り方は4通り (y=Y の行を回文にする/しない, y=H-1-Y の行を回文にする/しない)
  • 固定した列は回文にするとして、必要に応じて行を回文にするために同じにならないといけないマスを union find でくっつけていく
  • みたいな。

  • 最内ループの最大処理回数は 3432*7*14*4*2*14 = 37,669,632
  • accepted in practice

#define f(x, y) ((x)+(y)*W)

const int inf = 1<<30;
int dp[16][16]; // dp[Y][r] ... y<Y or H-1-Y<y までの範囲に関して r 個以上の行と C 個以上の列が回文であるようにした時の最小コスト
int co[32][2]; // [CellID][value] ... CellID を代表とするグループのセルのうち value であるものの個数 (value in {0, 1})
class PalindromeMatrix {
	public:
	int minChange(vector <string> A, int R, int C) {
		int W=A[0].size(), H=A.size();
		VI wcc(W);
		REP(i, C) wcc[i]=1;
		sort(ALL(wcc));
		int ans = inf;
		do {
			REP(y, H/2+1) REP(r, R+1) dp[y][r]=inf;
			dp[0][0] = 0;
			REP(y, H/2) REP(r, R+1) {
				if(dp[y][r]==inf) continue;
				int Y[] = {y, H-1-y}; // upper/lower
				REP(pat, 4) {
					bool upper = pat&1, lower = pat&2; // is upper/lower row palindrome?
					int new_r = r+upper+lower;
					if(new_r<=R) {
						int add = 0;
						UnionFind uf(W*2); // x in [0, W), y in Y, index==y*W+x
						REP(x, W) if(wcc[x]) uf.Union(f(x, 0), f(x, 1)); // 選ばれた C 列は必ず回文にする
						if(upper) REP(x, W) uf.Union(f(x, 0), f(W-1-x, 0)); // 必要なら行を回文にする
						if(lower) REP(x, W) uf.Union(f(x, 1), f(W-1-x, 1));
						CLEAR(co, 0);
						REP(y, 2) REP(x, W) co[uf.root(f(x, y))][A[Y[y]][x]-'0']++;
						REP(y, 2) REP(x, W) if(uf.root(f(x, y))==f(x, y)) add += min(co[f(x, y)][0], co[f(x, y)][1]);
						dp[y+1][new_r] = min(dp[y+1][new_r], dp[y][r] + add);
					}
				}
			}
			ans = min(ans, dp[H/2][R]);
		} while(next_permutation(ALL(wcc)));
		return ans;
	}
};

2013-12-01

Recruit Programming Contest 模擬練習会 B. ブロック並べ

22:36 |  Recruit Programming Contest 模擬練習会 B. ブロック並べ - kojingharangの日記 を含むブックマーク はてなブックマーク -  Recruit Programming Contest 模擬練習会 B. ブロック並べ - kojingharangの日記  Recruit Programming Contest 模擬練習会 B. ブロック並べ - kojingharangの日記 のブックマークコメント

  • 赤ブロックR個、青ブロックB個を一列に並べる。連続する rn 個のうち赤が rk 個以上あるとだめ。連続する bn 個のうち青が bk 個以上あるとだめ。そんな並べ方は何通りあるか, mod 1,000,000,009で求めよ。
  • 1≦R,B≦20
  • 1≦rkrn≦9, 1≦bk≦bn≦9
  • 最後に連続した赤/青の個数を持っておくのか?
  • いや rn 個中何個だから置いていってずれたとき押し出されるのも状態に含めないとだめだ
  • 直前の rn, bn 個をビットパターンで持っておけばよさげ
  • 合わない
  • ご飯
  • 赤青を使った個数も持っておかないとだめだ
  • 合わない
  • 青置いた個数は計算できるから要らないか
  • [置いた個数(%2)][赤を置いた個数][直前のrn個がそれぞれ赤かどうか][直前のbn個がそれぞれ青かどうか] = それが何通りあるか
  • 合わない
  • おわり
  • 初期化してなかった。おうふ
  • 初期化をしましょう
  • accepted in practice

#define MOD 1000000009LL

int dp[2][21][512][512];

int main() {
	//ios::sync_with_stdio(false);
	int TESTS, R, B, rn, rk, bn, bk;
	cin>>TESTS;
	REP(test, TESTS) {
		CLEAR(dp, 0);
		int men=0;
		dp[men][0][0][0]=1;
		cin>>R>>B>>rn>>rk>>bn>>bk;
		REP(i, R+B) {
			CLEAR(dp[1-men], 0); // 初期化しましょう
			REP(cr, R+1) REP(ri, 1<<rn) REP(bi, 1<<bn) {
				int cb = i-cr;
				if(!(0<=cb && cb<=B)) continue;
				if(dp[men][cr][ri][bi]==0) continue;
				int nri = (ri<<1)&((1<<rn)-1);
				int nbi = (bi<<1)&((1<<bn)-1);
				if(POPCOUNT(nri)+1<rk && cr<R) dp[1-men][cr+1][nri|1][nbi] = (dp[1-men][cr+1][nri|1][nbi] + dp[men][cr][ri][bi]) % MOD;
				if(POPCOUNT(nbi)+1<bk && cb<B) dp[1-men][cr][nri][nbi|1] = (dp[1-men][cr][nri][nbi|1] + dp[men][cr][ri][bi]) % MOD;
			}
			men ^= 1;
		}
		ll ans = 0;
		REP(ri, 1<<rn) REP(bi, 1<<bn) ans = (ans + dp[men][R][ri][bi]) % MOD;
		
		cout<<ans<<endl;
	}
	
	return 0;
}

2013-11-15

Codeforces #212 Div2 C. Insertion Sort

23:14 |  Codeforces #212 Div2 C. Insertion Sort - kojingharangの日記 を含むブックマーク はてなブックマーク -  Codeforces #212 Div2 C. Insertion Sort - kojingharangの日記  Codeforces #212 Div2 C. Insertion Sort - kojingharangの日記 のブックマークコメント

  • 0〜N-1のpermutation配列Aを挿入ソートする。ソートの前に任意の2要素を交換できるとき、swapの回数の最小値と、そうなる2要素の個数を求める。
  • 2≦N≦5000
  • 何もしないときの転倒数を求めておいて、
  • i<j なる i, j in [0, N) それぞれに対して、予め A[i], A[j] を交換したときの転倒数の増減を求めれば答えがわかる。
  • 増減は
  • A[i], A[i+1], ..., A[j-1], A[j] (最初)
  • A[j]をA[i]の前に持ってきたときの増減は、Σ(A[k]<A[j]の個数)-(A[k]>A[j]の個数) for k in [i, j)
  • A[j], A[i], A[i+1], ..., A[j-1] (A[j]をA[i]の前に移動)
  • A[i]をA[j-1]の後ろに持ってきたときの増減は、Σ(A[k]>A[i]の個数)-(A[k]<A[j]の個数) for k in [i+1, j)
  • A[j], A[i+1], ..., A[j-1], A[i](A[i]をA[j-1]の後ろに移動)
  • 普通にやるとO(N^3)なのでなんとかO(N^2)にしたい
  • wf[r][l] ... A[r]をA[l]の前に持ってきたときの転倒数の増減
  • wf[l][r] ... A[l]をA[r]の後ろに持ってきたときの転倒数の増減
  • とO(N^2)で事前に計算しておく。
  • と、A[i], A[j]を交換したときの増減は wb[r][l] + wf[l][r-1] で求まるので全体として O(N^2)
  • accepted

int main() {
	ll N;
	while(cin>>N) {
		VI w(N);
		REP(i, N) cin>>w[i];
		VVI wb(N, VI(N)), wf(N, VI(N));
		REP(i, N) for(int k=i-1;k>=0;k--) wb[i][k] = wb[i][k+1] + (w[k]>w[i]) - (w[k]<w[i]);
		REP(i, N) for(int k=i+1;k<N;k++) wf[i][k] = wf[i][k-1] + (w[k]<w[i]) - (w[k]>w[i]);
		int Max=0;
		int co=0;
		RANGE(r, 1, N) REP(l, r) {
			Max = max(Max, wb[r][l]+wf[l][r-1]);
		}
		RANGE(r, 1, N) REP(l, r) {
			if(Max==wb[r][l]+wf[l][r-1]) co++;
		}
		int def=0;
		RANGE(i, 1, N) {
			int j=i;
			while(j>0 && w[j] < w[j-1]) {
				swap(w[j], w[j-1]);
				def++;
				j--;
			}
		}
		cout<<def-Max<<" "<<co<<endl;
	}
	
	return 0;
}

2013-11-02

SRM 593 Div1 250 IncrementAndDoubling

09:50 |  SRM 593 Div1 250 IncrementAndDoubling - kojingharangの日記 を含むブックマーク はてなブックマーク -  SRM 593 Div1 250 IncrementAndDoubling - kojingharangの日記  SRM 593 Div1 250 IncrementAndDoubling - kojingharangの日記 のブックマークコメント

  • 長さNで全部0の配列に対して(1)どれかに1を足す,(2)全部を2倍する, を繰り返して別途与えられる長さNの配列Dと等しくしたい。最小手数を求める。
  • 1≦N≦50, 0≦D[i]≦1000
  • できるかぎり2倍は使った方が良いだろう(証明なし)
  • ビット表現で1になってるとこは必ず1を足す操作で作られるはず。タイミングは2倍2倍に増えてく途中でここぞというときにやればいい
  • なので一番大きな数に対して2倍を適用する回数 + 全ての数のbit countが答え
  • accepted

class IncrementAndDoubling {
	public:
	int getMin(vector <int> D) {
		int ans = 0;
		REP(i, D.size()) ans += POPCOUNT(D[i]);
		int mx = 0;
		REP(i, D.size()) mx=max(mx, D[i]);
		while(mx>1) { ans++;mx>>=1;}
		return ans;
	}
};



SRM 593 Div1 500 BitwiseAnd

09:50 |  SRM 593 Div1 500 BitwiseAnd - kojingharangの日記 を含むブックマーク はてなブックマーク -  SRM 593 Div1 500 BitwiseAnd - kojingharangの日記  SRM 593 Div1 500 BitwiseAnd - kojingharangの日記 のブックマークコメント

  • Nと、N以下の要素を持つlong long配列Sが与えられる。
  • S に必要ならいくつか要素を追加して、(1)任意の2組のAND>0, (2)任意の3組のAND==0, (3)数値は1以上2^60-1以内, になるような辞書順最小の配列を求めよ。
  • 3≦N≦50, 1≦S[i]≦2^60-1
  • ビット表現にして配列を各行に書いてくと...
  • 各列に1がちょうど2個あって、任意のi, jにたいして行iと行jが連結(どちらも1の列がある)してればOK
  • 連結状態を行列で管理してみる
  • すでに3つ以上あるときはだめ
  • 追加する数字に関しては、
  • すべての列に対して、縦に見て1の数を数えて、1個なら連結じゃないので連結できそうな行を見つけて1を書いていく
  • 連結じゃないときは全ての列が0であるとこを探して無理やり1を2つ書いて連結にする
  • でいいと思われる
001100011  //99
010011101  //157
?????????
?????????

001100011  //99
010011101  //157
100000110  //262
100101000  //296

Returns: {99, 157, 262, 296 }
  • バグりまくる
  • 終了
  • 書いた
  • サンプルの{}, 5が合わない
  • 紙に書いて分析。辞書順最小になってない。
  • 連結じゃないときに無理やり追加するのは毎回じゃなくて最後に1回だけにしないと辞書順最小にならないようだ
  • いろいろ証明できてなくてもやもやしてるけどpracticeで通った
  • ↓あとで(accepted in practice room)
class BitwiseAnd {
	public:
	ll add;
	VVI w;
	int N, M;
	VI S;
	bool check(bool init) {
		REP(bit, 60) {
			VI one;
			REP(i, S.size()) {
				if((S[i]>>bit)&1) one.PB(i);
			}
			if(one.size()>2) return false;
			if(one.size()==2) w[one[1]][one[0]] = w[one[0]][one[1]] = 1;
			if(!init && one.size()==1 && w[one[0]][S.size()]==0) {
				add |= 1LL<<bit;
				w[one[0]][S.size()] = w[S.size()][one[0]] = 1;
			}
		}
		if(!init) {
			S.PB(add);
		}
		REP(i, S.size()) REP(j, S.size()) {
			if(i!=j && w[i][j]==0) {
				if(init) {
					return false;
				}
			}
		}
		return true;
	}
	vector<long long> lexSmallest(vector<long long> _S, int N) {
		S=_S;
		M=S.size();
		w = VVI(N, VI(N));
		if(!check(true)) return vector<ll>();
		while(S.size()<N) {
			add=0;
			if(!check(false)) return vector<ll>();
		}
		REP(i, S.size()) REP(j, S.size()) {
			if(i!=j && w[i][j]==0) {
				if(i<M || j<M) continue;
				bool ok=false;
				REP(bit, 60) {
					bool lok=true;
					REP(k, S.size()) if((S[k]>>bit)&1) lok=false;
					if(lok) {
						ll a = 1LL<<bit;
						S[i] |= a;
						S[j] |= a;
						w[i][j]=w[j][i]=1;
						ok=true;
					}
					if(ok) break;
				}
			}
		}
		if(!check(true)) return vector<ll>();
		sort(ALL(S));
		return S;
	}
};
|