POJ 2114: Boatherds

http://poj.org/problem?id=2114

蟻本と同じことやるだけ…と思うがO(MNlog^2N)になって通るわけないだろ!と思い、ググってみるとどうやらそれでいけるらしいと分かったので、submitしてみるとTLE。途方に暮れているとvectorにstaticつけると速くなるよって書いてあったのでつけてみると余裕でAC。わけわからん。

namespace CD { //centroid decomposition

	struct edge { int to, length; };

	vector<edge> G[MAX_N];
	bool centroid[MAX_N];
	int subtree_size[MAX_N];

	void init(int n) {
		rep(i, 0, n) G[i].clear();
	}
	void add_edge(int a, int b, int c) {
		G[a].push_back((edge){b, c});
		G[b].push_back((edge){a, c});
	}

	int compute_subtree_size(int v, int p) {
		int c = 1;
		rep(i, 0, (int)G[v].size()) {
			int w = G[v][i].to;
			if(w == p || centroid[w]) continue;
			c += compute_subtree_size(G[v][i].to, v);
		}
		subtree_size[v] = c;
		return c;
	}

	pi search_centroid(int v, int p, int t) { 
		pi res = pi(inf, -1);
		int s = 1, m = 0;
		rep(i, 0, (int)G[v].size()) {
			int w = G[v][i].to;
			if(w == p || centroid[w]) continue;

			MIN(res, search_centroid(w, v, t));

			MAX(m, subtree_size[w]);
			s += subtree_size[w];
		}
		MAX(m, t - s);
		MIN(res, pi(m, v));
		return res;
	}

	void update(int s);

	bool solve_subproblem(int v) {
		if(ans > 0) return true;
		compute_subtree_size(v, -1);
		int s = search_centroid(v, -1, subtree_size[v]).sec;
		centroid[s] = true;

		rep(i, 0, (int)G[s].size()) {
			if(centroid[G[s][i].to]) continue;
			solve_subproblem(G[s][i].to);
		}
		update(s);
		centroid[s] = false;
		return ans > 0;
	}

	////////////////////////////////////////////// edit from here
	
	//distance from centroid
	void enumerate_pathes(int v, int p, int d, vi &ds) {
		ds.push_back(d);
		rep(i, 0, (int)G[v].size()) {
			int w = G[v][i].to;
			if(w == p || centroid[w]) continue;
			enumerate_pathes(w, v, d + G[v][i].length, ds);
		}
	}

	//num of pairs whose lengths is less than k
	int count_pairs(vi &ds) {
		int res = 0;
		sort(ds.begin(), ds.end());
		int j = (int)ds.size();
		rep(i, 0, (int)ds.size()) {
			while(j > 0 && ds[i] + ds[j - 1] > K) j--;
			res += j - (j > i ? 1 : 0);
		}
		j = (int)ds.size();
		rep(i, 0, (int)ds.size()) {
			while(j > 0 && ds[i] + ds[j - 1] >= K) j--;
			res -= j - (j > i ? 1 : 0);
		}
		return res;
	}

	void update(int s) {
		static vi ds;
		ds.clear();
		ds.push_back(0);
		rep(i, 0, (int)G[s].size()) {
			if(centroid[G[s][i].to]) continue;

			static vi tds;
			tds.clear();
			enumerate_pathes(G[s][i].to, s, G[s][i].length, tds);

			ans -= count_pairs(tds);
			ds.insert(ds.end(), tds.begin(), tds.end());
		}
		ans += count_pairs(ds);
	}
}


int main() {
	while(true) {
		scanf("%d", &N);
		CD::init(N);
		if(N == 0) break;
		rep(i, 0, N) {
			while(true) {
				int a, b;
				scanf("%d", &a); if(a == 0) break;
				a--;
				scanf("%d", &b);
				CD::add_edge(i, a, b);
			}
		}
		while(true) {
			ans = 0;
			scanf("%d", &K);
			if(K == 0) break;
			if(CD::solve_subproblem(0)) printf("AYE\n");
			else printf("NAY\n");
		}
		printf(".\n");
	}
	return 0;
}

c++もっと詳しくならないとと思った。

POJ 1741: Tree

http://poj.org/problem?id=1741

重心分解。解法は蟻本にある。

適当に整備して使いやすいようにした。updateだけいじればよくなっている。

updateも重心を根とする木について考えれば良いので楽。

namespace CD { //centroid decomposition

	struct edge { int to, length; };

	vector<edge> G[MAX_N];
	bool centroid[MAX_N];
	int subtree_size[MAX_N];

	void init(int n) {
		rep(i, 0, n) G[i].clear();
	}
	void add_edge(int a, int b, int c) {
		G[a].push_back((edge){b, c});
		G[b].push_back((edge){a, c});
	}

	int compute_subtree_size(int v, int p) {
		int c = 1;
		rep(i, 0, (int)G[v].size()) {
			int w = G[v][i].to;
			if(w == p || centroid[w]) continue;
			c += compute_subtree_size(G[v][i].to, v);
		}
		subtree_size[v] = c;
		return c;
	}

	//(maxnum of subtree vertexs, centroid vertex)
	pi search_centroid(int v, int p, int t) { 
		pi res = pi(inf, -1);
		int s = 1, m = 0;
		rep(i, 0, (int)G[v].size()) {
			int w = G[v][i].to;
			if(w == p || centroid[w]) continue;

			MIN(res, search_centroid(w, v, t));

			MAX(m, subtree_size[w]);
			s += subtree_size[w];
		}
		MAX(m, t - s);
		MIN(res, pi(m, v));
		return res;
	}

	void update(int s);

	void solve_subproblem(int v) {
		compute_subtree_size(v, -1);
		int s = search_centroid(v, -1, subtree_size[v]).sec;
		centroid[s] = true;
		//(1)
		rep(i, 0, (int)G[s].size()) {
			if(centroid[G[s][i].to]) continue;
			solve_subproblem(G[s][i].to);
		}
		update(s);
		centroid[s] = false;
	}

	////////////////////////////////////////////// edit from here
	
	//distance from centroid
	void enumerate_pathes(int v, int p, int d, vi &ds) {
		ds.push_back(d);
		rep(i, 0, (int)G[v].size()) {
			int w = G[v][i].to;
			if(w == p || centroid[w]) continue;
			enumerate_pathes(w, v, d + G[v][i].length, ds);
		}
	}

	//num of pairs whose lengths is less than k
	int count_pairs(vi &ds) {
		int res = 0;
		sort(ds.begin(), ds.end());
		int j = ds.size();
		rep(i, 0, (int)ds.size()) {
			while(j > 0 && ds[i] + ds[j - 1] > K) j--;
			res += j - (j > i ? 1 : 0);
		}
		return res / 2;
	}

	void update(int s) {
		vi ds;
		ds.push_back(0);
		rep(i, 0, (int)G[s].size()) {
			if(centroid[G[s][i].to]) continue;

			vector<int> tds;
			enumerate_pathes(G[s][i].to, s, G[s][i].length, tds);

			ans -= count_pairs(tds);
			ds.insert(ds.end(), tds.begin(), tds.end());
		}
		ans += count_pairs(ds);
	}
}

蟻本もやっと4章。4章は純愛。

POJ 3470: Wall

http://poj.org/problem?id=3470

平面走査するだけのタイピングゲーなんだけど、TLEがかなり厳しい。AC率308/2036が物語っている。

vectorを配列にするとかベタな高速化をしてもACに至らず…segtreeが重いのかな?

TLE解法

struct T {
	pi v;
	T(pi v = pi(inf, -1)) : v(v) {}
	T operator+(const T& t) {//when getting value
		return T(min(v, t.v));
	}
	void operator+=(const T& t) {//when updating value
		v = t.v;
	}
};

struct segtree {
	int n; T sum[MAX_V], lazy[MAX_V];
	void init(int mx) {
		n = 1;
		while(n < mx) n *= 2;
		fill(sum, sum + n * 2, T());
		fill(lazy, lazy + n * 2, T());
	}
	segtree(int mx = 0) { init(mx); }
	inline void lazy_eval(int k, int l, int r) {
		if(lazy[k].v != T().v) {
			sum[k * 2 + 1] += lazy[k];
			lazy[k * 2 + 1] += lazy[k];
			sum[k * 2 + 2] += lazy[k];
			lazy[k * 2 + 2] += lazy[k];
			lazy[k] = T();
		}
	}
	void app(int a, int b, T s, int k, int l, int r) {
		if(b <= l || r <= a) return;
		if(a <= l && r <= b) {
			sum[k] += s; //here too
			lazy[k] += s;
		}
		else {
			lazy_eval(k, l, r);
			app(a, b, s, k * 2 + 1, l, (l + r) / 2);
			app(a, b, s, k * 2 + 2, (l + r) / 2, r);
			sum[k] = sum[k * 2 + 1] + sum[k * 2 + 2];
		}
	}
	T ga(int a, int b, int k, int l, int r) {
		if(b <= l || r <= a) return T();
		if(a <= l && r <= b) return sum[k];
		else {
			lazy_eval(k, l, r);
			T lv = ga(a, b, k * 2 + 1, l, (l + r) / 2);
			T rv = ga(a, b, k * 2 + 2, (l + r) / 2, r);
			return lv + rv;
		}
	}
	void show() {
		vector<pi> tmp;
		rep(i, 0, n) {
			tmp.push_back(get(i, i + 1));
		}
		debug(tmp);
	}
	pi get(int a, int b) { return ga(a, b, 0, 0, n).v; } //[a, b)
	void update(int a, int b, pi s) { app(a, b, T(s), 0, 0, n); } //[a, b)
};


struct query {
	bool wall; int x1, x2, index;
};

int N, M;
int X1[MAX_N], Y1[MAX_N], X2[MAX_N], Y2[MAX_N];
int X[MAX_N], Y[MAX_N];
int vx[MAX_N], vy[MAX_N];
segtree seg;

vector<query> Q[MAX_N];
pi P[MAX_N][4];

int ans[MAX_N];

int fd(int *V, int n, int a) {
	return find(V, V + n, a) - V;
}

void solve(int at) {
	int n = 0, m = 0;
	rep(i, 0, N) {
		vx[n++] = X1[i];
		vx[n++] = X2[i];
		vy[m++] = Y1[i];
		vy[m++] = Y2[i];
		vx[n++] = X[i];
		vy[m++] = Y[i];
	}
	sort(vx, vx + n); sort(vy, vy + m);
	n = unique(vx, vx + n) - vx;
	m = unique(vy, vy + m) - vy;

	rep(i, 0, m) {
		Q[i].clear();
	}

	rep(i, 0, N) {
		int nx1 = fd(vx, n, X1[i]), nx2 = fd(vx, n, X2[i]);
		int ny1 = fd(vy, m, Y1[i]), ny2 = fd(vy, m, Y2[i]);
		if(nx1 == nx2) {
			int ny = max(ny1, ny2);
			Q[ny].push_back((query){true, nx1, nx1 + 1, i});
		}
		else if(ny1 == ny2) {
			int nmx = max(nx1, nx2), nmi = min(nx1, nx2);
			Q[ny1].push_back((query){true, nmi, nmx + 1, i});
		}
	}

	rep(i, 0, M) {
		int nx = fd(vx, n, X[i]), ny = fd(vy, m, Y[i]);
		Q[ny].push_back((query){false, nx, nx + 1, i});
	}

	seg.init(n);

	rep(i, 0, m) {
		rep(j, 0, (int)Q[i].size()) {
			query &q = Q[i][j];
			//debug(q.wall, q.x1, q.x2, i, q.index);
			if(q.wall) {
				seg.update(q.x1, q.x2, pi(i, q.index));
				//seg.show();
			}
			else {
				pi p = seg.get(q.x1, q.x2);
				if(p == T().v) {
					P[q.index][at] = T().v;
				}
				else {
					P[q.index][at] = pi(vy[i] - vy[p.fst], p.sec);
				}
			}
		}
	}
}

void neg_flip() {
	rep(i, 0, N) {
		X1[i] *= -1;
		Y1[i] *= -1;
		X2[i] *= -1;
		Y2[i] *= -1;
	}
	rep(i, 0, M) {
		X[i] *= -1;
		Y[i] *= -1;
	}
}

void xy_flip() {
	rep(i, 0, N) {
		swap(X1[i], Y1[i]);
		swap(X2[i], Y2[i]);
	}
	rep(i, 0, M) {
		swap(X[i], Y[i]);
	}
}

int main() {
	scanf("%d%d", &N, &M);
	rep(i, 0, N) {
		scanf("%d%d%d%d", &X1[i], &Y1[i], &X2[i], &Y2[i]);
	}
	rep(i, 0, M) {
		scanf("%d%d", &X[i], &Y[i]);
	}
	int at = 0;
	solve(at++);

	neg_flip();
	solve(at++);

	xy_flip();
	solve(at++);

	neg_flip();
	solve(at++);
	
	rep(i, 0, M) {
		//debug(P[i]);
		int at = -1, dist = inf;
		rep(j, 0, 4) {
			if(dist > P[i][j].fst) {
				dist = P[i][j].fst;
				at = P[i][j].sec;
			}
		}
		ans[at]++;
	}
	rep(i, 0, M) {
		printf("%d\n", ans[i]);
	}
	return 0;
}

POJ 3411: Paid Roads

http://poj.org/problem?id=3411

問題文をよく読みましょう。(a,b)は有向辺です。無向辺でやったあとを残しておきました。
ベルマンフォードっぽくbitDPをやればよい。

int N, M;
int A[20], B[20], C[20], P[20], R[20];
int dp[1 << 11][11];

int main() {
	scanf("%d%d", &N, &M);
	rep(i, 0, M) {
		scanf("%d%d%d%d%d", &A[i], &B[i], &C[i], &P[i], &R[i]);
		A[i]--; B[i]--; C[i]--;
	}
	rep(bit, 0, (1 << N))
		rep(i, 0, N) dp[bit][i] = inf;

	dp[1][0] = 0;

	rep(k, 0, 3 * N) {
		rep(bit, 1, (1 << N)) {
			rep(i, 0, N) {
				if(!(bit & (1 << i)) || dp[bit][i] >= inf) continue;

				rep(j, 0, M) {
					int a = A[j], b = B[j], c = C[j];
					if(i == a) {
						int mask = bit;
						if(!(mask & (1 << b))) mask |= (1 << b);
						MIN(dp[mask][b], dp[bit][i] + ((bit & (1 << c)) ? P[j] : R[j]));
					}
					/*
					else if(i == b) {
						int mask = bit;
						if(!(mask & (1 << a))) mask |= (1 << a);
						else MIN(dp[mask][a], dp[bit][i] + ((bit & (1 << c)) ? P[j] : R[j]));
					}
					*/
				}
			}
		}
	}
	int res = inf;
	rep(bit, 0, (1 << N)) MIN(res, dp[bit][N - 1]);
	if(res == inf) printf("impossible\n");
	else printf("%d\n", res);

	return 0;
}

POJほんとに問題文読めない…

AOJ 2266: Cache Strategy

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=2266

箱が1つなら簡単。
箱がmつの時はM-1個の箱がコストを節約するために使えると考えられる。
どれくらいコストを節約できるか考えよう。
あるボールを時刻sに入れ、次に入れる時刻をtとしよう。
すると時刻[s+1,t)においてボールを入れ続けることが出来ればボールのコストを削減できる。
なのでこのような区間の重なりがM-1以上にならないようにコストを最大化する問題になる。

これは蟻本のPOJ 3680と同じ問題なので解けた。

問題の咀嚼が難しい。こういう言い換えは思いつかない。
ボールだけに注目したり、箱のみに注目したり…みたいに「部分」に注目すると気付きやすい?

解説を読んでいると
http://blog.hatena.ne.jp/omochangram/omochan.hatenablog.com/edit?entry=8599973812276127257
と少し似ているなと思った。

使っているんだけど使っていないことにしたり、使うものをストックしといて後に使ったり。時系列でものを使っていくときのテクニック?

int M, N, K;
int A[MAX_N];
vector<int> B[MAX_N];

void solve() {
	cin >> M >> N >> K;
	int res = 0;
	MCF::init(K);
	rep(i, 0, N) cin >> A[i];
	rep(i, 0, K) {
		int a; cin >> a; a--;
		B[a].push_back(i);
		res += A[a];
	}
	rep(i, 0, N) {
		if((int)B[i].size() <= 1) continue;
		rep(j, 0, (int)B[i].size() - 1) {
			int n1 = B[i][j], n2 = B[i][j + 1];
			if(n1 + 1 != n2) MCF::add_edge(n1 + 1, n2, 1, -A[i]);
			else res -= A[i];
		}
	}
	rep(i, 0, K - 1) MCF::add_edge(i, i + 1, M, 0);
	cout << res + MCF::get(0, K - 1, M - 1) << "\n";
}

How to Create a Good Gameは何とかして倒したい。

POJ 3680: Intervals

http://poj.org/problem?id=3680

解法は蟻本にある。

蟻本では負辺にあらかじめ目一杯流すことで対応しているが、ここではベルマンフォードで最初のポテンシャルを求めることによって解いてみよう。

ポテンシャルh(v):=(s-v間の最短距離)と基本的にすれば良いが、以下のコードの方が単純。

memset(h, 0, sizeof(h));
rep(k, 0, V) {
	rep(i, 0, V) {
		rep(j ,0, (int)G[i].size()) {
			edge &e = G[i][j];
			if(e.cap == 0) continue;
			MIN(h[e.to], h[i] + e.cost);
		}
	}
}

これでもいい理由を考えよう。
ある有向辺(u,v)(cost = ce)について、
ce<0の時、h(v)<=h(u)+ceが成立。よってce'=h(u)-h(v)+ce>=0より条件を満たす。

ce>0の時、h(v)が普通のベルマンフォードで正になるような更新が来てもh(v)=0となる。よって得られるはずであったh(v)の値をh'(v)とすれば、h(v)<=0<=h'(v)<=h(u)+ceが成立。ce<0と同じ理由で条件を満たす。

最小費用流の計算量をまじめに考えたことがなかったので考察すると、
|F|に最短経路を見つけるだけのオーダーがかかる。
つまりdijkstraで最短経路を求めているのならO(|F||E|log|V|)
bellman-fordならO(|F||V|^2)である。
密なグラフならbellman-fordの方が速いことに留意する。

全体

namespace MCF { //init before you use it

	struct edge { int to, cap, cost, rev; };

	int V; //num of vertex
	vector<edge> G[MAX_V];
	int h[MAX_V];
	int dist[MAX_V];
	int prevv[MAX_V], preve[MAX_V];

	void init(int v) { //other things don't have to be init.
		V = v;
		rep(i, 0, v) G[i].clear();
	}

	void add_edge(int from, int to, int cap, int cost) {
		G[from].push_back((edge){to, cap, cost, (int)G[to].size()});
		G[to].push_back((edge){from, 0, -cost, (int)G[from].size() - 1});
	}

	int get(int s, int t, int f) {
		int res = 0;
		memset(h, 0, sizeof(h));
		//add if the graph includes negative edges
		rep(k, 0, V) {
			rep(i, 0, V) {
				rep(j ,0, (int)G[i].size()) {
					edge &e = G[i][j];
					if(e.cap == 0) continue;
					MIN(h[e.to], h[i] + e.cost);
				}
			}
		}
		
		while(f > 0) {
			priority_queue<pi, vector<pi>, greater<pi> > que;
			fill(dist, dist + V, inf);
			dist[s] = 0;
			que.push(pi(0, s));
			while(!que.empty()) {
				pi p = que.top(); que.pop();
				int v = p.sec;
				if(dist[v] < p.fst) continue;
				rep(i, 0, (int)G[v].size()) {
					edge &e = G[v][i];
					if(e.cap > 0 && dist[e.to] > dist[v] + e.cost + h[v] - h[e.to]) {
						dist[e.to] = dist[v] + e.cost + h[v] - h[e.to];
						prevv[e.to] = v;
						preve[e.to] = i;
						que.push(pi(dist[e.to], e.to));
					}
				}
			}

			if(dist[t] == inf) return -1;
			rep(v, 0, V) h[v] += dist[v];

			int d = f;
			for(int v = t; v != s; v = prevv[v]) {
				d = min(d, G[prevv[v]][preve[v]].cap);
			}
			f -= d;
			res += d * h[t];
			for(int v = t; v != s; v = prevv[v]) {
				edge &e = G[prevv[v]][preve[v]];
				e.cap -= d;
				G[v][e.rev].cap += d;
			}
		}
		return res;
	}
}

int N, K, Q;
int a[MAX_N], b[MAX_N], w[MAX_N];

int main() {
	scanf("%d", &Q);
	while(Q--) {
		scanf("%d%d", &N, &K);
		rep(i, 0, N) scanf("%d%d%d", &a[i], &b[i], &w[i]);

		vector<int> x;
		rep(i, 0, N) {
			x.push_back(a[i]);
			x.push_back(b[i]);
		}
		sort(all(x));
		x.erase(unique(all(x)), x.end());

		int m = (int)x.size();
		int s = m, t = m + 1;
		MCF::init(m + 2);
		MCF::add_edge(s, 0, K, 0);
		MCF::add_edge(m - 1, t, K, 0);

		rep(i, 0, m - 1) MCF::add_edge(i, i + 1, inf, 0);

		rep(i, 0, N) {
			int u = find(x.begin(), x.end(), a[i]) - x.begin();
			int v = find(x.begin(), x.end(), b[i]) - x.begin();
			MCF::add_edge(u, v, 1, -w[i]);
		}
		printf("%d\n", -MCF::get(s, t, K));
	}
	return 0;

AtCoder Regular Contest 080E: Young Maids

http://arc080.contest.atcoder.jp/tasks/arc080_c

逆から見ると、列の偶数番目と奇数番目をとって、3つの列に分割して再帰的にまた同じようなことをする問題に言い換えられる。
しかしうまく実装する方法が思いつかず、ひたすらデバッガ使って通した。強い人のコードを参考にします。

int N;
int A[MAX_N];
vector<int> G[MAX_N * 3];
pi P[MAX_N * 3];
int E;
segtree os, es;


void dfs(int l, int r, int k, bool odd) {
	assert(k == E);
	int a = l / 2, b, c, d = r / 2;
	pi fp, sp;
	if(!odd) {
		fp = es.get(a, d); b = fp.sec;
		sp = os.get(b, d); c = sp.sec;
		if(b == -1 || c == -1) {
			P[k] = T().v;
			return;
		}

		es.update(b, T());
		os.update(c, T());
		a *= 2; d *= 2; b *= 2; c = c * 2 + 2;
	}
	else {
		fp = os.get(a, d); b = fp.sec;
		sp = es.get(b + 1, d); c = sp.sec;
		if(b == -1 || c == -1) {
			P[k] = T().v;
			return;
		}

		os.update(b, T());
		es.update(c, T());
		a *= 2; d *= 2; b = b * 2 + 2; c *= 2;
	}

	P[E] = pi(fp.fst, sp.fst);

	if(a != b) {
		E++; G[k].push_back(E);
		dfs(a, b, E, odd);
	}
	if(b != c) {
		E++; G[k].push_back(E);
		dfs(b, c, E, 1 ^ odd);
	}
	if(c != d) {
		E++; G[k].push_back(E);
		dfs(c, d, E, odd);
	}
}

pi ans[MAX_N];

void solve() {
	cin >> N;
	os.init(N / 2);
	es.init(N / 2);

	rep(i, 0, N) {
		cin >> A[i];
		if(i % 2 == 0) es.update(i / 2, pi(A[i], i / 2));
		else os.update(i / 2, pi(A[i], i / 2));
	}
	dfs(0, N, 0, false);
	int M = 0;
	priority_queue<ppi, vector<ppi>, greater<ppi> > que;
	que.push(make_pair(P[0], 0));
	while(!que.empty()) {
		ppi pp = que.top(); que.pop();
		//debug(M, pp.fst);
		ans[M++] = pp.fst;
		int v = pp.sec;
		rep(i, 0, (int)G[v].size()) {
			if(P[G[v][i]] != T().v) {
				que.push(make_pair(P[G[v][i]], G[v][i]));
			}
		}
	}
	rep(i, 0, M) {
		cout << ans[i].fst << " " << ans[i].sec << " ";
	}
	cout << "\n";
}

(8/27追記)N/2ではなくN要素のsegtree(間はinfで埋める)でやれば実装簡単。