agc038 F Two Permutations (二元关系最小割)

题意

给你两个排列,分别用他们做一个新排列,求两个新排列的最多不同位置个数。
用排列a生成新排列要满足:一个位置i要么是i,要么是a[i].
n 1 0 5 n\leq 10^5

思路

  • 首先观察变换,发现要么是一个轮换不变,要么是轮换位移一下。
  • 即可以选择每个轮换是否换。
  • 发现这个之后,我尝试了构通解的图,发现并构不出来…
  • 只能暴力考虑每种情况:考虑每个位置的贡献:
  • 因为每个位置最多有两种取值,因此情况数是比较少的。
  1. P i = Q i = i P_i=Q_i=i ,此位置必定有1的代价
  2. P i = Q i i P_i=Q_i\not=i ,若两边都换或都不换,则有1的代价
  3. P i = i Q i i P_i=i且Q_i\not=i
  4. Q i = i P i i Q_i=i且P_i\not=i ,这两种是某一边不换则有1的代价。
  5. P i , Q i i P i Q i P_i,Q_i\not=i且P_i\not=Q_i ,都不换则有1的代价

构图非常套路,这种状态相同的有额外代价的,只需要把两边“反过来”。

  • P在S集是不选,反之是选。
  • Q在S集是,反之是不选。

在对应的轮换上连边即可,这样是二分图。(如果不将一个轮换中的点合并,就不是二分图,跑的很慢。。。)

跑最小割即可,用dinic。(一定要加当前弧,不然复杂度有问题)

O ( m n ) O(m\sqrt n)

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10, inf = 1e9;
int n, P[N], Q[N];
int S, T, final[N], nex[N * 8], to[N * 8], tot = 1, f[N * 8];
void _link(int x, int y, int w) {
	to[++tot] = y, nex[tot] = final[x], final[x] = tot;
	f[tot] = w;
}

void link(int x, int y, int w) {
	_link(x, y, w), _link(y, x, 0);
}

int vis[N], ans;
int dis[N], cur[N];
void dinic() {
	static queue<int> Q;
	memset(cur, 0, sizeof cur);
	memset(dis, 127, sizeof dis);
	dis[S] = 0; Q.push(S);
	while (Q.size()) {
		int x = Q.front(); Q.pop();
		for(int i = final[x]; i; i = nex[i]) if(f[i]){
			int y = to[i];
			if(dis[x] + 1 < dis[y]) {
				dis[y] = dis[x] + 1;
				Q.push(y);
			}
		}
	}
}

int go(int x, int w) {
	if (x == T) return w;
	int used = 0;
	if (cur[x] == 0) cur[x] = final[x];
	for(int i = cur[x]; i; i = nex[i]) {
		if (f[i]) {
			int y = to[i];
			if (dis[x] + 1 == dis[y]) {
				int take = go(y, min(w - used, f[i]));
				f[i] -= take;
				f[i ^ 1] += take;
				used += take; 
				if (used == w) return w;
			}
		}
		cur[x] = i;
	}
	return used;
}

int lab[N], lab2[N];
int labcnt;
int main() {
	int g = clock();
	freopen("f.in", "r", stdin);
	cin >> n;
	for(int i = 1; i <= n; i++) scanf("%d", &P[i]), P[i]++;
	for(int i = 1; i <= n; i++) scanf("%d", &Q[i]), Q[i]++;
	S = n + n + 1, T = S + 1;
	for(int i = 1; i <= n; i++) if(!vis[i]) {
		labcnt++;
		int t = i;
		vis[t] = 1;
		lab[t] = labcnt;
		while (vis[P[t]] == 0) {
			// _link(P[t], t, inf);
			// _link(t, P[t], inf);
			t = P[t];
			vis[t] = 1;
			lab[t] = labcnt;
		}
	}

	memset(vis, 0, sizeof vis);
	for(int i = 1; i <= n; i++) if(!vis[i]) {
		labcnt++;
		int t = i; vis[t] = 1;
		lab2[t] = labcnt;
		while (vis[Q[t]] == 0) {
			// _link(Q[t] + n, t + n, inf);
			// _link(t + n, Q[t] + n, inf);
			t = Q[t];
			vis[t] = 1;
			lab2[t] = labcnt;
		}
	}

	ans = n;
	for(int i = 1; i <= n; i++) {
		if (P[i] == i && Q[i] == i) {
			ans --;
		} else {
			if (P[i] == i && Q[i] != i) { //n+i默认Q[i](在S集合),否则就有1的代价
				link(S, lab2[i], 1);
			} else 
			if (P[i] != i && Q[i] == i) { //i默认P[i](在T集合)
				link(lab[i], T, 1);
			} else
			if (P[i] != i && Q[i] != i && P[i] != Q[i]) { //同时选i有1的代价
				link(lab[i], lab2[i], 1);
			} else { // P[i] == Q[i] 选法相同有1的代价
				_link(lab[i], lab2[i], 1);
				_link(lab2[i], lab[i], 1);
			}
		}
	}
	cerr << labcnt << endl;
	while (dinic(), dis[T] <= inf) {
		ans -= go(S, inf);
		cerr << ans << endl;
	}
	cout << ans << endl;
	cerr << clock() - g << endl;
}

发布了266 篇原创文章 · 获赞 93 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/jokerwyt/article/details/102990732
今日推荐