AtCoder Regular Contest 080F: Prime Flip

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

まず列の差をとって区間の問題を距離が奇素数の2点を反転させてすべて0にする問題に帰着させる。

座標i,jにある1を対応させるときのコストは
|i-j|が奇素数→1
|i-j|が偶数→2
|i-j|が奇素数ではない奇数→3
となる。これは今N<=10^7であり、ゴールドバッハ予想が成立することから示せる。

あとは座標が奇数の頂点と偶数の頂点に分け、なるべくコスト1の辺を使ってマッチングを作ればいい。これは最大フローで解ける。

int N, M;
int E, O;
int A[110], B[210];
int C[210], D[210];

bool prime(int v) {
	if(v <= 2) return false;
	for(int i = 2; i * i <= v; i++) {
		if(v % i == 0) return false;
	}
	return true;
}

void solve() {
	cin >> N;
	M = 0;
	rep(i, 0, N) cin >> A[i];
	rep(i, 0, N) {
		int t1 = (find(A, A + N, A[i] - 1) != A + N);
		int t2 = (find(A, A + N, A[i] + 1) != A + N);
		if(t1 == 0) B[M++] = A[i] - 1;
		if(t2 == 0) B[M++] = A[i];
	}
	M = unique(B, B + M) - B;
	int s = M, t = M + 1;
	MF::init(M + 2);

	rep(i, 0, M) {
		if(B[i] % 2 == 0) C[E++] = B[i];
		else D[O++] = B[i];
	}
	rep(i, 0, E) {
		rep(j, 0, O) {
			if(prime(abs(C[i] - D[j]))) {
				MF::add_edge(i, j + E, 1);
			}
		}
	}
	rep(i, 0, E) MF::add_edge(s, i, 1);
	rep(i, 0, O) MF::add_edge(i + E, t, 1);

	int res = MF::get(s, t);
	if((E - res) % 2 == 0) {
		cout << res + (E - res) + (O - res) << "\n";
	}
	else {
		cout << res + (E - res - 1) + (O - res - 1) + 3 << "\n";
	}
}

そもそも差をとるところから間違ってて笑った。これくらいのステップ数の問題が解けるようになると楽しそう。

AtCoder Regular Contest 078E: Awkward Response

http://arc078.contest.atcoder.jp/tasks/arc078_c

まず桁数を特定する。これは1, 10, 100,...と比較するとわかる。
それからは上の位から数字を特定していく。
例えば(10^3,10^4)の区間において49990を投げると、

(10^3,5*10^3)でyes,[5*10^3,10^4)でNoとなる。
同じようなことをにぶたんで行えば各桁が4回の質問で求められるので、64回の制限を余裕で満たす。

下のコードは自分でもわけのわからず通したもの…

bool ok(ll res) {
	//char c = get_ans(res);
	cout << "? " << res << endl;
	char c; cin >> c;
	return c == 'Y';
}


void solve() {
	ll res = 0;
	for(ll i = 0; i < 10; i++) {
		ll lv, rv = 10;
		if(i == 0) lv = 1;
		else lv = 0;
		while(rv - lv > 1) {
			ll m = (lv + rv) / 2;
			if(ok(res * 10 + m)) lv = m;
			else rv = m;
		}
		res = res * 10 + lv;
		//char c = get_ans(res + 1);
		cout << "? " << res + 1 << endl;
		char c; cin >> c;

		if((all_nine(res) && c == 'N') || (!all_nine(res) && c == 'Y')) {
			res -= 9;
			if(i == 0) lv = 0;
			else lv = -1;
			rv = 9;
			while(rv - lv > 1) {
				ll m = (lv + rv) / 2;
				if(ok(res * 10 + m * 10)) rv = m;
				else lv = m;
			}
			cout << "! " << res + rv << endl;
			return;
		}
	}
	cout << "!  1" << endl;
}

文字列を比較するときは文字の長さを特定するのも重要だね。

AtCoder Regular Contest 078F: Mole and Abandoned Mine

http://arc078.contest.atcoder.jp/tasks/arc078_d

3^Nの形になるdpなんて初めて見た。

まず使う辺に注目して、それらのコストの和を最大化する。
dp[S][v]:=(集合Sの点を使う際で0~v間でpathがただ一つ存在するための最大値)とする。

遷移は二種類ある。
(1)path上の点を追加する。
(2)vにpathが増えないよう点をくっつける。

(1)は問題ないが、(2)は4^Nかかるように見える。
しかし実際は3^Nとなり、適当に定数倍改善すれば十分高速。

なぜ3^Nになるかというと、いま注目すべきものは、
「S⊃T⊃Uとなる(T,U)が何個あるか」という問題と同値である。

各bitについて「Tに選ばれない」「Tにのみ選ばれる」「T,Uともに選ばれる」の三種類があるので、結局(T,U)のペアは3^Nとなる。

int N, M;
int dp[(1 << 16)][16];
int cost[(1 << 16)];
int A[500], B[500], C[500];
int E[50];
int D[50][50];

void solve() {
	cin >> N >> M;
	memset(D, -1, sizeof(D));
	int res = 0;
	rep(i, 0, M) {
		cin >> A[i] >> B[i] >> C[i];
		A[i]--; B[i]--;
		D[A[i]][B[i]] = C[i];
		D[B[i]][A[i]] = C[i];
		res += C[i];
	}
	rep(bit, 0, (1 << N)) {
		rep(j, 0, M) {
			if((bit & (1 << A[j])) && (bit & (1 << B[j]))) {
				cost[bit] += C[j];
			}
		}
	}
	rep(bit, 0, (1 << N)) 
		rep(j, 0, N) dp[bit][j] = -inf;

	dp[1][0] = 0;


	rep(bit, 0, (1 << N)) {
		rep(i, 0, N) {
			if(!(bit & (1 << i)) || dp[bit][i] < 0) continue;
			rep(j, 0, N) {
				if(!(bit & (1 << j)) && D[i][j] != -1) {
					MAX(dp[bit | (1 << j)][j], dp[bit][i] + D[i][j]);
				}
			}
			int n = 0;
			rep(j, 0, N) {
				if(!(bit & (1 << j))) {
					E[n++] = j;
				}
			}
			rep(mask, 0, (1 << n)) {
				int nbit = 0;
				rep(k, 0, n) {
					if(mask & (1 << k)) {
						nbit |= (1 << E[k]);
					}
				}
				MAX(dp[nbit | bit][i], dp[bit][i] + cost[nbit | (1 << i)]);
			}
		}
	}
	int tmp = dp[(1 << N) - 1][N - 1];
	cout << res - tmp << "\n";

}

POJ 3422: Kaka's Matrix Travels

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

http://hos.ac/slides/20150319_flow.pdf
ここのp69に載っている問題と同じ。

負辺があるが、グラフが特殊な形をしているので、dijkstraで求めても最短経路が求まり、ポテンシャルも正しく求まる。

これではつまらないので負辺を取り除く方法を考えた。

http://snuke.hatenablog.com/entry/2017/06/07/115821

上のすぬけさんのブログと蟻本を参考にした。

言い換えると今解きたい問題は、dv(s)=-K, dv(t)=K,それ以外dv(v)=0の時の最小費用循環流問題なので、
スーパーノードS,Tを作り、そこから適切にsやtに辺を張ることで実現できる。

これでもACが取れましたといいたいところだが、辺が増えすぎてTLEした。

負辺を含む最小費用流について(Attack the Moles) - sigma425のブログ
ここに書いてあるが、今している変形はこのブログの3に相当し、FがNくらい増えて、オーダーが悪くなることがわかる。

ACしたの

int N, K;

int main() {
	scanf("%d%d", &N, &K);
	int s = 0, t = N * N * 2 - 1;
	MCF::init(N * N * 2);
	rep(i, 0, N) {
		rep(j, 0, N) {
			int id = i * N + j;
			int a; scanf("%d", &a);
			MCF::add_edge(id, id + N * N, 1, -a);
			MCF::add_edge(id, id + N * N, K, 0);
			if(i != N - 1) {
				MCF::add_edge(id + N * N, id + N, K, 0);
			}
			if(j != N - 1) {
				MCF::add_edge(id + N * N, id + 1, K, 0);
			}
		}
	}
	printf("%d\n", -MCF::get(s, t, K));
	return 0;
}

負辺を取り除いたの(TLE)

//minimum cost circularation
namespace MCC { //init before you use it. //negative edges ok.

	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];
	int S[MAX_V];
	int off;

	void init(int v) {
		V = v;
		//memset(S, 0, sizeof(S));
		//rep(i, 0, V + 2) G[i].clear();
	}

	void add_edge(int from, int to, int cap, int cost) {
		if(cost >= 0) {
			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});
		}
		else {
			off += cap * cost;
			S[from] += cap; S[to] -= cap;
			G[to].push_back((edge){from, cap, -cost, (int)G[from].size()});
			G[from].push_back((edge){to, 0, cost, (int)G[to].size() - 1});
		}
	}

	void set_d(int v, int d) { S[v] += d; }

	int impl(int s, int t, int f) {
		int res = 0;
		memset(h, 0, sizeof(h));
		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 get() { //u,v,cap,cost
		int f = 0;
		int s = V, t = V + 1;
		rep(i, 0, V) {
			if(S[i] > 0) add_edge(i, t, S[i], 0);
			if(S[i] < 0) {
				add_edge(s, i, -S[i], 0);
				f += -S[i];
			}
		}
		/*
		rep(i, 0, V) {
			rep(j, 0, (int)G[i].size()) {
				debug(i, G[i][j].to, G[i][j].cost);
			}
		}
		*/
		V += 2;
		return impl(s, t, f) + off;
	}
}

int N, K;

int main() {
	scanf("%d%d", &N, &K);
	int s = 0, t = N * N * 2 - 1;
	MCC::init(N * N * 2);
	MCC::set_d(s, -K);
	MCC::set_d(t, K);

	rep(i, 0, N) {
		rep(j, 0, N) {
			int id = i * N + j;
			int a; scanf("%d", &a);
			MCC::add_edge(id, id + N * N, 1, -a);
			MCC::add_edge(id, id + N * N, K, 0);
			if(i != N - 1) {
				MCC::add_edge(id + N * N, id + N, K, 0);
			}
			if(j != N - 1) {
				MCC::add_edge(id + N * N, id + 1, K, 0);
			}
		}
	}
	printf("%d\n", -MCC::get());
	return 0;
}

POJ 2195: Going Home

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

これも最大マッチングに毛を生やした程度。
最小コスト完全マッチング問題とか言われるやつ。

namespace MCF(Minimum Cost Flow)を使っているのだがMFと書きがちなので注意。

nt H, M;
int HX[MAX_N], HY[MAX_N];
int MX[MAX_N], MY[MAX_N];

char s[MAX_N];

int main() {
	while(true) {
		scanf("%d%d", &X, &Y);
		if(X == 0 && Y == 0) break;
		H = 0; M = 0;
		rep(i, 0, X) {
			scanf("%s", s);
			rep(j, 0, Y) {
				if(s[j] == 'm') {
					MX[M] = i;
					MY[M] = j; M++;
				}
				else if(s[j] == 'H') {
					HX[H] = i;
					HY[H] = j; H++;
				}
			}
		}
		MCF::init(H + M + 2);
		int s = H + M, t = H + M + 1;
		rep(i, 0, H) {
			rep(j, 0, M) {
				int dist = abs(HX[i] - MX[j]) + abs(HY[i] - MY[j]);
				MCF::add_edge(i, j + H, 1, dist);
			}
		}
		rep(i, 0, H) MCF::add_edge(s, i, 1, 0);
		rep(i, 0, M) MCF::add_edge(i + H, t, 1, 0);
		printf("%d\n", MCF::get(s, t, H));
	}
	return 0;
}

最小費用流はアルゴリズムが難しい(ポテンシャルのところとか)けど使うだけならそこまで苦労しないのかな。

POJ 3068: "Shortest" pair of paths

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

POJ 2135(蟻本p214)とほぼ一緒。
ただ今度は点も共有してはいけないので点の数を二倍にして、点の間に容量1、コスト0の辺を張って対処する。

最大点素パスに毛が生えたくらい。

inf = 2^30にしたらオーバーフローしてビビった。2^29にしてAC

namespace MCF { //init before you use it. //inf should be 1 << 29 or less

	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) {
		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));
		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, M;

int main() {
	for(int q = 1; ; q++) {
		scanf("%d%d", &N, &M);
		if(N == 0 && M == 0) break;
		MCF::init(N * 2);

		rep(i, 0, M) {
			int a, b, c; 
			scanf("%d%d%d", &a, &b, &c);
			MCF::add_edge(a + N, b, 1, c);
		}

		rep(i, 0, N) MCF::add_edge(i, i + N, 1, 0);

		int ans = MCF::get(N, N - 1, 2);
		printf("Instance #%d: ", q);
		if(ans == -1) printf("Not possible\n");
		else printf("%d\n", ans);
	}
	return 0;
}

すこしTexいじったので、ブログでも使おうかと思ったけど意外にめんどくさそう。

POJ 3155: Hard Life

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

良問だと思う。

hardness factorがkの時実現可能か考える。
辺と点を対応させた二部グラフを作る。
辺からそれが含む点に対して辺をはる。
そして辺にコスト1,点にコスト-kをつけると、Maximum Closure Problemに帰着できるので、
あとはPOJ 2987と同じようにグラフを構成し、「得られた値が0より大きい<=>kの時実現可能」となるから
kをにぶたんで求めることが出来る。

あとはcap>0が成立する辺のみでdfsすることで求めるべき点の集合を得られる。

フローのcapの型をdoubleにし忘れないように。

namespace MF { //init before you use it. when you use double, be careful
	struct edge { int to; double cap; int rev; };

	vector<edge> G[MAX_N];
	int level[MAX_N];
	int iter[MAX_N];

	void init(int n) {
		rep(i, 0, n) G[i].clear();
		memset(level, 0, sizeof(level));
		memset(iter, 0, sizeof(iter));
	}

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

	void bfs(int s) {
		memset(level, -1, sizeof(level));
		queue<int> que;
		level[s] = 0;
		que.push(s);
		while(!que.empty()) {
			int v = que.front(); que.pop();
			for(int i = 0; i < (int)G[v].size(); i++) {
				edge &e = G[v][i];
				if(e.cap > 0 && level[e.to] == -1) {
					level[e.to] = level[v] + 1;
					que.push(e.to);
				}
			}
		}
	}

	double dfs(int v, int t, double f) {
		if(v == t) return f;
		for(int &i = iter[v]; i < (int)G[v].size(); i++) {
			edge &e = G[v][i];
			if(e.cap > 0 && level[v] < level[e.to]) {
				double d = dfs(e.to, t, min(f, e.cap));
				if(d > 0) {
					e.cap -= d;
					G[e.to][e.rev].cap += d;
					return d;
				}
			}
		}
		return 0;
	}

	double get(int s, int t) {
		double flow = 0;
		while(true) {
			bfs(s);
			if(level[t] == -1) return flow;
			memset(iter, 0, sizeof(iter));
			double f;
			while((f = dfs(s, t, inf)) > 0) {
				flow += f;
			}
		}
	}
}

int N, M;

int A[1010], B[1010];
bool used[1110];

void dfs(int v) {
	used[v] = true;
	rep(i, 0, (int)MF::G[v].size()) {
		MF::edge& e = MF::G[v][i];
		if(e.cap >= eps && !used[e.to]) {
			dfs(e.to);
		}
	}
}

bool ok(double m) {
	MF::init(N + M + 2);
	int s = N + M, t = N + M + 1;
	rep(i, 0, M) {
		MF::add_edge(i + N, A[i], inf);
		MF::add_edge(i + N, B[i], inf);
	}
	rep(i, 0, N) MF::add_edge(i, t, m);
	rep(i, 0, M) MF::add_edge(s, i + N, 1);
	double ans = MF::get(s, t);
	double res = (double)M - ans;
	return res >= eps;
}

int main() {
	scanf("%d%d", &N, &M);
	rep(i, 0, M) {
		scanf("%d%d", &A[i], &B[i]);
		A[i]--;
		B[i]--;
	}

	double lv = 0, rv = M;
	while(rv - lv > eps) {
		double m = (lv + rv) / 2;
		if(ok(m)) lv = m;
		else rv = m;
	}
	ok(lv);
	dfs(N + M);
	if(M != 0) {
		int res = 0;
		rep(i, 0, N) res += used[i];
		printf("%d\n", res);
		rep(i, 0, N) {
			if(used[i]) printf("%d\n", i + 1);
		}
	}
	else {
		printf("1\n1\n");
	}


	return 0;
}

最小費用流にやっと入れる…。