LCA Doubling + Sparse Table(?)

http://omochan.hatenablog.com/entry/2017/07/15/181512

この問題で師匠のコードを見ていたら、Sparse Tableっぽいの使っているなあと思ったので僕も実装してみました。
vcost[v][k]:vから2^k個先の親までのpathで一番小さい辺のcostとすると、ダブリングと同じようにvcost[v][k]も求められます。
これを使えばrange getやrange updateもできるようになります。ただしupdateとgetが交互に来ると使えないです。
updateした内容から正しいTableを得るためにはO(NlogN)かかるからです。

木のqueryを解く手法としてDoubling, Euler Tour, HL-decompositionの3つが考えられます。
Doublingはrange get, range updateの両方ができるがgetとupdateが交互に来る場合は適応できず、
Euler Tourはrange getはできるが、range updateはできません。getとupdateが交互に来ても対応できます。
HLはrange get, range updateができ、getとupdateが交互に来ても大丈夫です。しかし計算量が少し重めです。(O(Nlog^2N)にしてはかなり軽いが)

struct T {
	ll v;
	T(ll v = inf) : v(v) {}
	T operator+(const T& t) { return T(min(v, t.v)); } //when getting value
	void operator+=(const T& t) { v = min(v, t.v); } //when updating value
	friend ostream& operator<<(ostream& out, const T& t) {
		out << t.v; return out;
	}
};

struct LCA { //set N for constructer

	const int LOG = 20; //par_next <= 2^(LOG - 1)
	int n, root;
	vector<vi> G, cost;
	vector<vi> par;
	vector<vector<T>> vcost;
	vector<int> depth;
	vector<vector<T>> S;

	LCA(int mx = 0): n(mx), G(mx), cost(mx), par(mx, vi(LOG)), vcost(mx, vector<T>(LOG, T())), depth(mx), S(mx, vector<T>(LOG, T())){}

	void add_edge(int a, int b, int c = 1) {
		G[a].pb(b); cost[a].pb(c);
		G[b].pb(a); cost[b].pb(c);
	}
	void dfs(int v, int p, int d) {
		par[v][0] = p;
		depth[v] = d;
		rep(i, 0, sz(G[v])) {
			int n = G[v][i];
			if(n != p) {
				vcost[n][0] = T(cost[v][i]);
				dfs(n, v, d + 1);
			}
		}
	}

	void init(int root = 0) {
		dfs(root, -1, 0);
		vcost[root][0] = T();
		rep(k, 0, LOG - 1) {
			rep(v, 0, n) {
				if(par[v][k] == -1) {
					par[v][k + 1] = -1;
					vcost[v][k + 1] = T();
				}
				else {
					par[v][k + 1] = par[par[v][k]][k];
					vcost[v][k + 1] = vcost[par[v][k]][k] + vcost[v][k];
				}
			}
		}
	}

	int lca(int u, int v) {
		if(depth[u] > depth[v]) swap(u, v);
		rep(k, 0, LOG) {
			if((depth[v] - depth[u]) >> k & 1) {
				v = par[v][k];
			}
		}
		if(u == v) return u;
		rer(k, LOG, 0)  {
			if(par[u][k] != par[v][k]) {
				u = par[u][k];
				v = par[v][k];
			}
		}
		return par[u][0];
	}

	T ga(int p, int v) {
		int d = depth[v] - depth[p];
		T res = T();
		rep(k, 0, LOG) {
			if(d & (1 << k)) {
				res += vcost[v][k];
				v = par[v][k];
			}
		}
		return res;
	}

	void app(int p, int v, T x) {
		int d = depth[v] - depth[p];
		rep(k, 0, LOG) {
			if(d & (1 << k)) {
				debug(p, v, x);
				S[v][k] += x;
				v = par[v][k];
			}
		}
	}

	ll query(int u, int v) {
		int p = lca(u, v);
		return (ga(p, u) + ga(p, v)).v;
	}
	void update(int u, int v, ll x) {
		int p = lca(u, v);
		debug(u, v, p, x);
		app(p, u, T(x));
		app(p, v, T(x));
	}

	void sweep() {
		rer(k, LOG, 1) {
			rep(v, 0, n) {
				S[v][k - 1] += S[v][k];
				if(par[v][k - 1] != -1) S[par[v][k - 1]][k - 1] += S[v][k];
			}
		}
	}
};

void solve() {
	int N = 9;
	LCA lca(N);
	lca.add_edge(0, 1, 8);
	lca.add_edge(0, 2, 7);
	lca.add_edge(1, 3, 6);
	lca.add_edge(1, 4, 4);
	lca.add_edge(2, 7, 5);
	lca.add_edge(4, 5, 3);
	lca.add_edge(4, 6, 2);
	lca.add_edge(5, 8, 1);
	lca.init(0);
	lca.update(8, 7, 3);
	lca.update(6, 2, 1);
	lca.update(8, 3, -1);
	lca.sweep();
	rep(i, 0, N) {
		rep(j, i + 1, N) {
			debug(i, j, lca.query(i, j));
		}
	}
	rep(i, 0, N) {
		debug(i, lca.S[i][0]);
	}
}

木を解く道具は全部そろったかな…

CF AIM Tech Round4C: Upgrading Tree

http://codeforces.com/contest/843/problem/C

重心を根としてウニみたいなグラフを構成します。具体的な方法は
http://kmjp.hatenablog.jp/entry/2017/09/05/0900
ここに書いてあります。
重心が複数個あるときもほとんど同じです。(複数といっても高々2つなんですが。2つの時は頂点数N/2の木2つを根でくっつけたものになります。)

こういうpathの長さに関するグラフ構築問題はウニグラフを作れると信じてやるのがよさそうですね。
実際過去に解いた問題に同じようなやつがありました。
Codeforces Round #423B: High Load - omochan's diary

すべてのpathの長さの総和みたいなのを求めるのは結構大変だし(マージテクでO(NlogN)かな?)、変な形のグラフが答えになると、judgeも正解か判定するのが困難になって出題できなくなると思います。

int N;
vector<int> G[MAX_N];
int S[MAX_N];
bool cen[MAX_N];
vector<int> C;
vector<int> sub[MAX_N], pre[MAX_N];
vector<vi> ans;

int dfs(int v, int p) {
	int cnt = 1;
	bool ok = true;
	rep(i, 0, sz(G[v])) {
		int n = G[v][i];
		if(n == p) continue;
		int a = dfs(n, v);
		if(a > N / 2) ok = false;
		cnt += a;
	}
	if(N - cnt > N / 2) ok = false;
	if(ok) {
		C.pb(v);
		cen[v] = true;
	}
	return cnt;
}

void dfs2(int v, int p, int c) {
	if(v != c && p != c) {
		sub[c].pb(v);
		pre[c].pb(p);
	}
	rep(i, 0, sz(G[v])) {
		int n = G[v][i];
		if(p == n) continue;
		dfs2(n, v, c);
	}
}

void solve() {
	cin >> N;
	rep(i, 0, N - 1) {
		int a, b; cin >> a >> b; a--; b--;
		G[a].pb(b);
		G[b].pb(a);
	}
	dfs(0, -1);
	// debug(C);
	rep(i, 0, sz(C)) {
		int v = C[i];
		rep(j, 0, sz(G[v])) {
			int n = G[v][j];
			if(cen[n]) continue;
			dfs2(n, v, n);

			int k = sz(sub[n]);
			if(k == 0) continue;
			ans.pb({v, n, sub[n][0]});
			rep(j, 0, k - 1) {
				int a = sub[n][j], b = pre[n][j], c = sub[n][j + 1];
				ans.pb({a, b, n});
				ans.pb({v, a, c});
			}
			int a = sub[n][k - 1], b = pre[n][k - 1];
			ans.pb({a, b, n});
			ans.pb({v, a, n});
		}
	}
	cout << sz(ans) << "\n";
	rep(i, 0, sz(ans)) {
		cout << ans[i][0] + 1 << " " << ans[i][1] + 1 << " " << ans[i][2] + 1 << "\n";
	}
}

それにしてもjudgeに時間がかかりすぎだろ。(5分くらい?)

ECR029F: Almost Permutation

http://codeforces.com/contest/863/problem/F

フローと言われちゃえばねぇ…。

まず配列の各iについてA[i]以上B[i]以下ではならないという条件を求める。B[i] < A[i]ならもちろん-1を返す。
それからはフローで値を求めるのだが、区間N個にN * 2 + j(0 <= j < N)という番号を振り当てると、
N * 2 + jからA[i] <= x <= B[i]を満たすxに辺を張り、xには1流すとコスト1,2流すとコスト3...k流すとコスト2*k+1になるように辺を張る。
そうすると、xにk流れた時コストはk^2になるのであとはこれの最小費用流を求めればよい。

int N, Q;
int A[MAX_N], B[MAX_N];

int cnt[MAX_N];

void solve() {
	cin >> N >> Q;
	rep(i, 0, N) B[i] = N - 1;
	while(Q--) {
		int q, a, b, c;
		cin >> q >> a >> b >> c;
		a--; b--; c--;
		if(q == 1) {
			for(int i = a; i <= b; i++) MAX(A[i], c);
		}
		else {
			for(int i = a; i <= b; i++) MIN(B[i], c);
		}
	}
	rep(i, 0, N) {
		if(B[i] < A[i]) {
			cout << -1 << "\n";
			return;
		}
	}
	MCC::init(N * 3 + 1);
	int t = N * 3;
	rep(i, 0, N) {
		MCC::set_d(N * 2 + i, -1);
		for(int j = A[i]; j <= B[i]; j++) {
			MCC::add_edge(N * 2 + i, j, inf, 0);
			MCC::add_edge(j, j + N, 1, cnt[j] * 2 + 1); cnt[j]++;
			MCC::add_edge(j + N, t, inf, 0);
		}
	}
	MCC::set_d(t, N);
	cout << MCC::get() << "\n";
}

循環最小費用流(各点について流量を設定できるヤツ)を使ったが、普通に下限設定できる最小費用流ライブラリが欲しいと思った。(こなみ)

CF243E: Sereja and Sets

http://codeforces.com/contest/425/problem/E

ずっと前に解けなかった問題。ひさしぶりに見たらそんな難しくなかった。

余分における区間の特性を考えよう。
区間スケジューリング問題の要領で、区間のうちとれるものの中で右側の座標が一番小さいものをとっていく。
今新しく区間をとったとして、その右側の座標をx1,一つ前にとった区間の右側の座標をx2とする。
いまx1が取れるもののうち最小なので、右側の座標がx1より小さい区間はとることができない。
右側の座標がx1の区間は(x1-x2)個とれ、
右側の座標がx1より大きい区間は、x1 * (N - x1)個取れるが、左側が0<=x<=x2を満たす区間はすでにとってあるので、
結局ダブりを除くと(x1 - x2) * (N - x1)個とれる。あとは適応に2のべき乗を考えてdpする。

int N, K;
ll pow2[300010];
ll dp[510][510];

void solve() {
	cin >> N >> K;
	pow2[0] = 1;
	rep(i, 1, N * N + 1) pow2[i] = pow2[i - 1] * 2 % mod;
	dp[0][0] = 1;
	rep(i, 0, N) {
		rep(j, 0, K) {
			if(!dp[i][j]) continue;
			rep(k, i + 1, N + 1) {
				int a = k - i;
				ADD(dp[k][j + 1], dp[i][j] * (pow2[a] - 1 + mod) % mod * pow2[a * (N - k)] % mod);
			}
		}
	}
	ll res = 0;
	rep(i, 0, N + 1) {
		ADD(res, dp[i][K]);
	}
	cout << res << "\n";
}

CFの2000pointsくらいまで解けたらいいね。

CF434E: Arkady and a Nobody-men

http://codeforces.com/contest/860/problem/E

HL分解やるだけで通ってしまった…
depthの浅い頂点から順番に見ていき、根まで+1をする更新を行うことで求められます。

segtreeでやったら遅かったのでbitにして1824ms/2000msでAC。

struct BIT { //0-origin!!! update and get just like lazy_segtree
	int n; vl bit0, bit1;
	void init(int mx) {
		n = mx;
		bit0 = vl(n + 1, 0); bit1 = vl(n + 1, 0);
	}
	BIT(int mx = 0) { init(mx); } 

	ll ga(vl& bit, int i) {
		ll s = 0;
		while(i > 0) { s += bit[i]; i -= i & -i; }
		return s;
	}
	void app(vl& bit, int i, ll x) {
		while(i <= n) { bit[i] += x; i += i & -i; }
	}
	void update(int a, int b, ll x) { //[a, b)
		a++;
		app(bit0, a, -x * (a - 1));
		app(bit1, a, x);
		app(bit0, b + 1, x * b);
		app(bit1, b + 1, -x);
	}
	ll get(int a, int b) { //[a, b)
		a++;
		return (ga(bit1, b) * b + ga(bit0, b)) 
			- (ga(bit1, a - 1) * (a - 1) + ga(bit0, a - 1));
	}
};

//////////////////////////////////////////////////////////////////////////HL BEGIN

struct HL {
	int n;
	vector<vi> to, cost, D;//D:group
	vi vcost, vd, vk, pv, dep;//vcost:edge cost,vd:D at,vk:num from parent,pv:group par,dep:depth
	//edge starts from (e:v->p) where v is top of D
	vector<BIT> S; //change 
	int root;

	HL(int mx = 0): n(mx), to(mx), cost(mx), vcost(mx), vd(mx), vk(mx), dep(mx) {}
	void add_edge(int a, int b, int c = 1) {
		to[a].pb(b); cost[a].pb(c);
		to[b].pb(a); cost[b].pb(c);
	}

	int dfs(int v, int depth = 0, int p = -1) {
		dep[v] = depth;
		vector<pi> rs;
		rep(i, 0, sz(to[v])) {
			int u = to[v][i];
			if(u == p) continue;
			vcost[u] = cost[v][i];
			rs.pb(pi(dfs(u, depth + 1, v), u));
		}
		if(sz(rs)) {
			sort(all(rs));
			vd[v] = vd[rs[0].sec]; pv[vd[v]] = p;
			D[vd[v]].pb(v); vk[v] = -sz(D[vd[v]]);
			return rs[0].fst - (sz(rs) != 1 && rs[0].fst == rs[1].fst);
		}
		else {
			vd[v] = sz(D); vk[v] = -1; D.pb(vi(1, v)); pv.pb(p);
			return 1;
		}
	}
	void init(int v = 0) {
		root = v;
		dfs(root);
		rep(i, 0, sz(D)) reverse(all(D[i]));
		rep(i, 0, sz(vk)) vk[i] += sz(D[vd[i]]);
		rep(i, 0, sz(D)) {
			S.pb(BIT(sz(D[i])));      
		}
	}
	int lca(int a, int b) {
		int p = vd[a]; vi ap(1, p);
		while(pv[p] != -1) ap.pb(p = vd[pv[p]]);
		reverse(all(ap)); ap.pb(-1);
		p = vd[b]; vi bp(1, p);
		while(pv[p] != -1) bp.pb(p = vd[pv[p]]);
		reverse(all(bp)); bp.pb(-2);
		int pi = 1; while(ap[pi] == bp[pi]) pi++;
		p = ap[pi - 1];
		int ai = vd[a] == p ? vk[a] : vk[pv[ap[pi]]];
		int bi = vd[b] == p ? vk[b] : vk[pv[bp[pi]]];
		return D[p][min(ai, bi)];
	}

	//////////////////////////////////////////////////////////////////////////////from here, change
	ll ga(int par, int v) {
		ll res = 0;
		while(true) {
			int nd = vd[v], nk = vk[v];
			if(nd == vd[par]) {
				return res + S[nd].get(vk[par], nk + 1);
			}
			else {
				res = res + S[nd].get(0, nk + 1);
			}
			v = pv[nd];
		}
	}
	void app(int par, int v, ll x) {
		while(true) {
			int nd = vd[v], nk = vk[v];
			if(nd == vd[par]) {
				S[nd].update(vk[par], nk + 1, x);
				return;
			}
			else {
				S[nd].update(0, nk + 1, x);
			}
			v = pv[nd];
		}
	}
	void query(int a) {
		app(root, a, 1);
		S[vd[a]].update(vk[a], vk[a] + 1, -1);
	}
	ll get(int a) {
		return ga(root, a);
	}
};

int N;
ll ans[MAX_N];
vector<int> D[MAX_N];
vector<int> G[MAX_N];

void solve() {
	cin >> N;
	HL hl(N);
	rep(i, 0, N) {
		int a;
		cin >> a; a--;
		if(a == -1) hl.root = i;
		else hl.add_edge(a, i);
	}
	hl.init(hl.root);
	rep(i, 0, N) {
		D[hl.dep[i]].pb(i);
	}
	int at = 1;
	while(!D[at].empty()) {
		rep(i, 0, sz(D[at])) {
			int v = D[at][i];
			hl.query(v);
		}
		rep(i, 0, sz(D[at])) {
			int v = D[at][i];
			ans[v] = hl.get(v);
		}
		at++;
	}
	rep(i, 0, N) cout << ans[i] << "\n";
}

HL分解ほんと定数倍軽いですね…

CF434D: Wizard's Tour

http://codeforces.com/contest/860/problem/D

解いたあとだとめっちゃ自明に見えるけどかなり苦労した。

dfs木を作った後、2辺ずつ木の下からとっていく。
ある点vを中間の点としたpathがとれるときはとる。
取れない場合はvの親pに向かう辺(v,p)を残す。
そうすると、結局M本辺があれば[M/2]個のpathが取れるので最大。

まず無向グラフに使える作戦はdfs木orUnionFind(マージテク含む)くらいしかないことを忘れていた。
dfs木を作った後も、dfsで一番使える情報は、子からの情報だというのに、上から(親の方から)考えようとして詰まった。

int N, M;
bool used[MAX_N];
vector<int> G[MAX_N];
int depth[MAX_N];
vector<tuple<int, int, int>> ans;

bool dfs(int v, int p, int d) {
	used[v] = true;
	depth[v] = d;
	vector<int> vs;
	rep(i, 0, sz(G[v])) {
		int n = G[v][i];
		if(n == p) continue;
		if(used[n]) {
			if(depth[n] < depth[v]) vs.pb(n);
		}
		else {
			if(dfs(n, v, d + 1)) vs.pb(n);
		}
	}
	vs.pb(p);
	rep(i, 0, sz(vs) / 2) {
		if(vs[i * 2 + 1] == -1 || vs[i * 2] == -1) continue;
		ans.pb(make_tuple(vs[i * 2], v, vs[i * 2 + 1]));
	}
	return sz(vs) % 2;
}

void solve() {
	cin >> N >> M;
	rep(i, 0, M) {
		int a, b;
		cin >> a >> b; a--; b--;
		G[a].pb(b);
		G[b].pb(a);
	}
	rep(i, 0, N) {
		if(!used[i]) dfs(i, -1, 0);
	}
	cout << sz(ans) << "\n";
	rep(i, 0, sz(ans)) {
		int x, y, z; tie(x, y, z) = ans[i];
		x++; y++; z++;
		cout << x << " " << y << " " << z << "\n";
	}
}

Atcoder Grand Contest 019E: Shuffle and Swap (部分点)

http://agc019.contest.atcoder.jp/tasks/agc019_e

aをS[i]=T[i]=1が成立している数
bをS[i]=1,T[i]=0が成立している数
cをS[i]=T[i]=1を満たす異なる二つのiについてswapを行った回数
としてdpを行う。
b=N-i-a+cが成立するので、O(N^3)で求められます。
1のうち動かせないもの、動かせるものみたいな単純な記号をもとに考察するとだいぶ思いつきやすいかも。
コードを見てみるとわかるように場合分けが大量にあるのでだいぶ筋が悪そうです。
やはり満点解法を取りに行くには、有向グラフの言い換えが思いつかないと厳しいのかなぁ…

int N, M;
ll dp[2][MAX_N][MAX_N];
string S, T;

void solve() {
	cin >> S >> T;
	M = sz(S);
	if(M > 500) {
		cout << "0" << "\n";
		return;
	}
	int a = 0, c = 0;
	rep(i, 0, M) {
		if(S[i] == '1') {
			if(T[i] == '1') c++;
			else a++;
		}
	}
	N = a + c;
	dp[0][c][0] = 1;
	int now = 0, nxt = 1;
	rep(i, 0, N) {
		memset(dp[nxt], 0, sizeof(dp[nxt]));
		rep(a, 0, N + 1) {
			rep(c, 0, N + 1) {
				if(a - c * 2 < 0) continue;
				int b = N - i - a + c;
				if(b < 0  || !dp[now][a][c]) continue;
				if(a > 0) ADD(dp[nxt][a - 1][c], dp[now][a][c] * (a - c * 2) % mod * b % mod);
				if(b > 0) ADD(dp[nxt][a][c], dp[now][a][c] * b % mod * b % mod);
				if(a - c * 2 > 0) ADD(dp[nxt][a - 1][c], dp[now][a][c] * (a - c * 2) % mod);
				if(a - c * 2 > 0) ADD(dp[nxt][a - 1][c], dp[now][a][c] * (a - c * 2) % mod * c % mod);
				if(a - c * 2 > 1) ADD(dp[nxt][a][c + 1], dp[now][a][c] * (a - c * 2) % mod * (a - c * 2 - 1) % mod);
				if(a - c * 2 > 0) ADD(dp[nxt][a - 1][c], dp[now][a][c] * (a - c * 2) % mod * c % mod);
				if(a > 1 && c > 0) ADD(dp[nxt][a - 2][c - 1], dp[now][a][c] * c % mod * c % mod);
			}
		}
		now = 1 - now; nxt = 1 - nxt;
	}
	cout << dp[now][0][0] << "\n";
}