2025-5-21 网络流做题笔记

2025-5-21 网络流做题笔记

le0n 的讲课 111

CF2046 D - For the Emperor!

先把强连通分量缩了,接下来考虑下放所有的信使,每个点拆成入点和出点,从入点流到出点表示有带信的信使经过,那么跑费用流即可。

ケロシの代码
const int N = 2e2 + 5;
const int M = 8e2 + 5;
const int V = 1e3;
const int INF = 1e9 + 7;
int n, m, a[N], b[N];
int fi[N], ne[M], to[M], ecnt;
int dfn[N], low[N], cnt;
int stk[N], vis[N], tp;
int sc, scc[N];
int gp(int u, int o) {
	return u + o * sc;
}
void add(int u, int v) {
	ne[++ ecnt] = fi[u];
	to[ecnt] = v;
	fi[u] = ecnt;
}
void tarjan(int u) {
	dfn[u] = low[u] = ++ cnt;
	stk[++ tp] = u;
	vis[u] = 1;
	for(int i = fi[u]; i; i = ne[i]) {
		int v = to[i];
		if(! dfn[v]) {
			tarjan(v);
			chmin(low[u], low[v]);
		}
		else if(vis[v]) {
			chmin(low[u], dfn[v]);
		}
	}
	if(dfn[u] == low[u]) {
		sc ++;
		while(1) {
			scc[stk[tp]] = sc;
			vis[stk[tp]] = 0;
			tp --;
			if(stk[tp + 1] == u) break;
		}
	}
}
namespace Mincost {
	const int N = 6e2 + 5;
	const int M = 1e4 + 5;
	const int INF = 0x3f3f3f3f;
	struct edge {
		int ew, ct, ne, to;
	} e[M];
	int fi[N], ecnt;
	int S, T;
	int d[N], f[N], p[N];
	bool vis[N];
	void init() {
		memset(fi, 0, sizeof fi);
		ecnt = 1;
	}
	void add(int u, int v, int w, int c) {
		e[++ ecnt] = {w, c, fi[u], v};
		fi[u] = ecnt;
		e[++ ecnt] = {0, - c, fi[v], u};
		fi[v] = ecnt;
	}
	bool spfa() {
		memset(d, 0x3f, sizeof d);
		memset(vis, 0, sizeof vis);
		queue<int> q;
		d[S] = 0; f[S] = INF;
		vis[S] = 1; q.push(S);
		while(! q.empty()) {
			int u = q.front();
			q.pop(); vis[u] = 0;
			for(int i = fi[u]; i; i = e[i].ne) if(e[i].ew) {
				int v = e[i].to;
				if(d[v] > d[u] + e[i].ct) {
					d[v] = d[u] + e[i].ct;
					f[v] = min(f[u], e[i].ew);
					p[v] = i;
					if(! vis[v]) {
						vis[v] = 1;
						q.push(v);
					}
				}
			}
		}
		return d[T] != INF;
	}
	PII mincost(int _S, int _T) {
		S = _S, T = _T;
		int flow = 0, cost = 0;
		while(spfa()) {
			flow += f[T];
			cost += f[T] * d[T];
			int u = T;
			while(u != S) {
				int v = p[u];
				e[v].ew -= f[T];
				e[v ^ 1].ew += f[T];
				u = e[v ^ 1].to;
			}
		}
		return {flow, cost};
	}
}
void solve() {
	cin >> n >> m;
	FOR(i, 1, n) cin >> a[i];
	ecnt = 0;
	FOR(i, 1, n) fi[i] = 0;
	REP(_, m) {
		int u, v;
		cin >> u >> v;
		add(u, v);
	}
	cnt = tp = sc = 0;
	FOR(i, 1, n) dfn[i] = low[i] = vis[i] = 0;
	FOR(i, 1, n) if(! dfn[i]) tarjan(i);
	FOR(i, 1, sc) b[i] = 0;
	FOR(i, 1, n) b[scc[i]] += a[i];
	Mincost :: init();
	int S = 0, T = sc * 3 + 1;
	FOR(i, 1, sc) {
		if(b[i]) {
			Mincost :: add(S, gp(i, 0), b[i], 0);
			Mincost :: add(gp(i, 0), gp(i, 1), 1, 1);
			Mincost :: add(gp(i, 0), gp(i, 2), INF, 0);
		}
		Mincost :: add(gp(i, 1), gp(i, 2), 1, - V);
		Mincost :: add(gp(i, 1), gp(i, 2), INF, 0);
		Mincost :: add(gp(i, 2), T, INF, 0);
	}
	FOR(u, 1, n) for(int i = fi[u]; i; i = ne[i]) {
		int v = to[i];
		if(scc[u] != scc[v]) 
			Mincost :: add(gp(scc[u], 2), gp(scc[v], 1), INF, 0);
	}
	auto h = Mincost :: mincost(S, T);
	if(SE(h) >= - V * (sc - 1)) cout << - 1 << endl;
	else cout << V + (SE(h) % V) << endl;
}

ICPC 2023 Polish E - Express Eviction

考虑将原图对偶,那么有一条路径相当于对偶图上左下到右上不连通,直接最小割即可。

ケロシの代码
namespace Dinic {
	const int N = 5e3 + 5;
	const int M = 1e6 + 5;
	const int INF = 0x3f3f3f3f;
	struct Edge { int ne, to, ew; } e[M];
	int fi[N], ecnt;
	int S, T, d[N], c[N];
	void init() {
		ecnt = 1;
		memset(fi, 0, sizeof fi);
	}
	void add(int u, int v, int w) {
		e[++ ecnt] = {fi[u], v, w};
		fi[u] = ecnt;
		e[++ ecnt] = {fi[v], u, 0};
		fi[v] = ecnt;
	}
	bool bfs() {
		memset(d, 0x3f, sizeof d);
		queue<int> q;
		d[S] = 0; q.push(S);
		while(! q.empty()) {
			int u = q.front();
			q.pop();
			for(int i = fi[u]; i; i = e[i].ne) if(e[i].ew) {
				int v = e[i].to;
				if(d[v] == INF) {
					d[v] = d[u] + 1;
					q.push(v);
				}
			}
		}
		return d[T] != INF;
	}
	int dfs(int u, int w) {
		if(u == T || ! w) return w;
		int res = 0;
		for(int & i = c[u]; i; i = e[i].ne) if(e[i].ew) {
			int v = e[i].to;
			if(d[v] != d[u] + 1) continue;
			int val = dfs(v, min(w, e[i].ew));
			if(! val) continue;
			e[i].ew -= val;
			e[i ^ 1].ew += val;
			w -= val;
			res += val;
			if(! w) return res;
		}
		return res;
	}
	int dinic(int _S, int _T) {
		S = _S, T = _T;
		int res = 0;
		while(bfs()) {
			memcpy(c, fi, sizeof c);
			res += dfs(S, INF);
		}
		return res;
	}
}
const int N = 55;
const int INF = 1e9 + 7;
int n, m;
string s[N];
int gp(int x, int y, int o) {
	return (x - 1) * m + y + o * n * m;
}
void solve() {
	cin >> n >> m;
	FOR(i, 1, n) {
		cin >> s[i];
		s[i] = ' ' + s[i];
	}
	int S = 0, T = gp(n, m, 1) + 1;
	Dinic :: init();
	FOR(i, 1, n) FOR(j, 1, m) if(s[i][j] == '#') {
		Dinic :: add(gp(i, j, 0), gp(i, j, 1), 1);
		if(j == 1 || i == n) Dinic :: add(S, gp(i, j, 0), INF);
		if(i == 1 || j == m) Dinic :: add(gp(i, j, 1), T, INF);
		FOR(dx, - 2, 2) FOR(dy, - 2, 2) {
			int x = i + dx, y = j + dy;
			if(x < 1 || x > n || y < 1 || y > m) continue;
			if(s[x][y] != '#') continue;
			Dinic :: add(gp(i, j, 1), gp(x, y, 0), INF);
		}
	}
	cout << Dinic :: dinic(S, T) << endl;
}

ABC397 G - Maximize Distance

首先考虑想要最段路变成 \(1\),那么就是一个最小割。

考虑扩展,每个点拆成一些距离点,表示 \(u\) 的最段路大于等于 \(d\)

那么 \((u,d) \to (v,d+1)\) 是没法割的,\((u,d) \to (v,d)\) 割掉需要 \(1\) 的代价。

不难发现一对点直接不会被割掉两次。

ケロシの代码
namespace Dinic {
	const int N = 1e3 + 5;
	const int M = 1e5 + 5;
	const int INF = 0x3f3f3f3f;
	struct Edge { int ne, to, ew; } e[M];
	int fi[N], ecnt;
	int S, T, d[N], c[N];
	void init() {
		ecnt = 1;
		memset(fi, 0, sizeof fi);
	}
	void add(int u, int v, int w) {
		e[++ ecnt] = {fi[u], v, w};
		fi[u] = ecnt;
		e[++ ecnt] = {fi[v], u, 0};
		fi[v] = ecnt;
	}
	bool bfs() {
		memset(d, 0x3f, sizeof d);
		queue<int> q;
		d[S] = 0; q.push(S);
		while(! q.empty()) {
			int u = q.front();
			q.pop();
			for(int i = fi[u]; i; i = e[i].ne) if(e[i].ew) {
				int v = e[i].to;
				if(d[v] == INF) {
					d[v] = d[u] + 1;
					q.push(v);
				}
			}
		}
		return d[T] != INF;
	}
	int dfs(int u, int w) {
		if(u == T || ! w) return w;
		int res = 0;
		for(int & i = c[u]; i; i = e[i].ne) if(e[i].ew) {
			int v = e[i].to;
			if(d[v] != d[u] + 1) continue;
			int val = dfs(v, min(w, e[i].ew));
			if(! val) continue;
			e[i].ew -= val;
			e[i ^ 1].ew += val;
			w -= val;
			res += val;
			if(! w) return res;
		}
		return res;
	}
	int dinic(int _S, int _T) {
		S = _S, T = _T;
		int res = 0;
		while(bfs()) {
			memcpy(c, fi, sizeof c);
			res += dfs(S, INF);
		}
		return res;
	}
}
const int M = 1e2 + 5;
const int INF = 1e9 + 7;
int n, m, k;
int fu[M], fv[M];
int gp(int x, int y) {
	return x + y * n;
}
void solve() {
	cin >> n >> m >> k;
	FOR(i, 1, m) cin >> fu[i] >> fv[i];
	Dinic :: init();
	int S = gp(1, 0), T = gp(n, n);
	FOR(u, 1, n) FOR(i, 1, n) Dinic :: add(gp(u, i - 1), gp(u, i), INF);
	int flow = 0;
	FOR(i, 1, n) {
		FOR(j, 1, m) Dinic :: add(gp(fu[j], i - 1), gp(fv[j], i - 1), 1);
		flow += Dinic :: dinic(S, T);
		if(flow > k) {
			cout << i - 1 << endl;
			return;
		}
		FOR(j, 1, m) Dinic :: add(gp(fu[j], i - 1), gp(fv[j], i), INF);
	}
}

ARC142 E - Pairing Wizards

考虑先把 \(a_x,a_y\) chmax 成 \(\min(b_x,b_y)\),这样接下来只要 \(a_x,a_y\) 有一个大于 \(\max(b_x,b_y)\) 即可。

不难发现如果 \(a_x < b_x\),那么 \(\max(b_x,b_y)\) 即为 \(b_x\),所以有两种选择:将 \(a_x\) 提到 \(b_x\),或者将所有 \(a_y\) 提到 \(b_x\),直接最小割即可。

ケロシの代码
namespace Dinic {
	const int N = 1.1e4 + 5;
	const int M = 1e5 + 5;
	const int INF = 0x3f3f3f3f;
	struct Edge { int ne, to, ew; } e[M];
	int fi[N], ecnt;
	int S, T, d[N], c[N];
	void init() {
		ecnt = 1;
		memset(fi, 0, sizeof fi);
	}
	void add(int u, int v, int w) {
		e[++ ecnt] = {fi[u], v, w};
		fi[u] = ecnt;
		e[++ ecnt] = {fi[v], u, 0};
		fi[v] = ecnt;
	}
	bool bfs() {
		memset(d, 0x3f, sizeof d);
		queue<int> q;
		d[S] = 0; q.push(S);
		while(! q.empty()) {
			int u = q.front();
			q.pop();
			for(int i = fi[u]; i; i = e[i].ne) if(e[i].ew) {
				int v = e[i].to;
				if(d[v] == INF) {
					d[v] = d[u] + 1;
					q.push(v);
				}
			}
		}
		return d[T] != INF;
	}
	int dfs(int u, int w) {
		if(u == T || ! w) return w;
		int res = 0;
		for(int & i = c[u]; i; i = e[i].ne) if(e[i].ew) {
			int v = e[i].to;
			if(d[v] != d[u] + 1) continue;
			int val = dfs(v, min(w, e[i].ew));
			if(! val) continue;
			e[i].ew -= val;
			e[i ^ 1].ew += val;
			w -= val;
			res += val;
			if(! w) return res;
		}
		return res;
	}
	int dinic(int _S, int _T) {
		S = _S, T = _T;
		int res = 0;
		while(bfs()) {
			memcpy(c, fi, sizeof c);
			res += dfs(S, INF);
		}
		return res;
	}
}
const int N = 1e2 + 5;
const int M = 1e4 + 5;
const int V = 100;
const int INF = 1e9 + 7;
int n, A[N], a[N], b[N];
int m, fu[M], fv[M];
int gp(int x, int y) {
	return x + y * n;
}
void solve() {
	cin >> n;
	FOR(i, 1, n) {
		cin >> A[i] >> b[i];
		a[i] = A[i];
	}
	cin >> m;
	FOR(i, 1, m) {
		cin >> fu[i] >> fv[i];
		int x = min(b[fu[i]], b[fv[i]]);
		chmax(a[fu[i]], x);
		chmax(a[fv[i]], x);
	}
	int ans = 0;
	FOR(i, 1, n) ans += a[i] - A[i];
	Dinic :: init();
	int S = 0, T = gp(n, V) + 1;
	FOR(i, 1, n) FOR(j, 2, V) Dinic :: add(gp(i, j), gp(i, j - 1), INF);
	FOR(i, 1, n) FOR(j, a[i] + 1, V) Dinic :: add(gp(i, j), T, 1);
	FOR(i, 1, n) if(a[i] < b[i]) {
		Dinic :: add(S, i, b[i] - a[i]);
		FOR(j, 1, m) {
			int u = fu[j], v = fv[j];
			if(u == i) Dinic :: add(i, gp(v, b[i]), INF);
			if(v == i) Dinic :: add(i, gp(u, b[i]), INF);
		}
	}
	ans += Dinic :: dinic(S, T);
	cout << ans << endl;
}

CF1427 G - One Billion Shades of Grey

考虑绝对值转化成 \(\sum_k [a<k][b \ge k]\),就可以跑流了。

考虑枚举 \(k\),每次直接退流即可。

ケロシの代码
const int N = 2e2 + 5;
const int M = 1e6 + 5;
int n, a[N][N];
struct Edge { int ne, to, ew; } e[M];
int fi[N * N], ecnt = 1;
int p[N * N], vis[N * N];
void add(int u, int v) {
	e[++ ecnt] = {fi[u], v, 0};
	fi[u] = ecnt;
	e[++ ecnt] = {fi[v], u, 0};
	fi[v] = ecnt;
}
int gp(int x, int y) {
	return (x - 1) * n + y;
}
int push(int u, int o) {
	if(p[u] == 2 && o) return 1;
	if(p[u] == 1 && ! o) return 1;
	vis[u] = 1;
	for(int i = fi[u]; i; i = e[i].ne) {
		int v = e[i].to;
		if(vis[v]) continue;
		if(o && e[i].ew == 1) continue;
		if(! o && e[i].ew != - 1) continue;
		if(push(v, o)) {
			e[i].ew ++;
			e[i ^ 1].ew --;
			return 1;
		}
	}
	return 0;
}
void solve() {
	cin >> n;
	FOR(i, 1, n) FOR(j, 1, n) cin >> a[i][j];
	FOR(i, 1, n) FOR(j, 1, n) if(a[i][j] >= 0) {
		if(i < n && a[i + 1][j] >= 0) 
			add(gp(i, j), gp(i + 1, j));
		if(j < n && a[i][j + 1] >= 0) 
			add(gp(i, j), gp(i, j + 1));
	}
	vector<PII> e;
	FOR(i, 1, n) FOR(j, 1, n) if(a[i][j] > 0) {
		p[gp(i, j)] = 2;
		e.push_back({a[i][j], gp(i, j)});
	}
	sort(begin(e), end(e));
	int flow = 0; ll ans = 0;
	REP(i, SZ(e) - 1) {
		int u = SE(e[i]);
		memset(vis, 0, sizeof vis);
		while(push(u, 0)) {
			flow --;
			memset(vis, 0, sizeof vis);
		}
		p[u] = 1;
		memset(vis, 0, sizeof vis);
		FOR(j, 0, i) {
			int v = SE(e[j]);
			while(push(v, 1)) {
				flow ++;
				memset(vis, 0, sizeof vis);
			}
		}
		ans += 1ll * flow * (FI(e[i + 1]) - FI(e[i]));
	}
	cout << ans << endl;
}

ICPC 2024 Shanghai K - Knights of Night

首先一个点只需要保留其前 \(k\) 大的边,所以分别算出左右两边的点保留出来的边,然后取交。

接下来一条边只会禁掉 \(2k\) 条边,所以直接保留前 \(2k^2\) 大的边。

接下来跑二分图最大匹配即可。

ケロシの代码
namespace Mincost {
	const int N = 2e5 + 5;
	const int M = 1e6 + 5;
	const int INF = 0x3f3f3f3f;
	const ll LNF = 0x3f3f3f3f3f3f3f3f;
	struct Edge { int ne, to, ew, ct; } e[M];
	int fi[N], ecnt;
	int n, S, T;
	ll d[N];
	int vis[N], f[N], p[N];
	void init(int siz) {
		ecnt = 1, n = siz;
		FOR(i, 0, n) fi[i] = 0;
	}
	void add(int u, int v, int w, int c) {
		e[++ ecnt] = {fi[u], v, w, c};
		fi[u] = ecnt;
		e[++ ecnt] = {fi[v], u, 0, - c};
		fi[v] = ecnt;
	}
	bool spfa() {
		FOR(i, 0, n) d[i] = LNF, vis[i] = 0;
		queue<int> q; q.push(S); 
		d[S] = 0; f[S] = INF; vis[S] = 1;
		while(! q.empty()) {
			int u = q.front();
			q.pop(); vis[u] = 0;
			for(int i = fi[u]; i; i = e[i].ne) if(e[i].ew) {
				int v = e[i].to;
				if(d[v] > d[u] + e[i].ct) {
					d[v] = d[u] + e[i].ct;
					f[v] = min(f[u], e[i].ew);
					p[v] = i;
					if(! vis[v]) {
						vis[v] = 1;
						q.push(v);
					}
				}
			}
		}
		return d[T] != LNF;
	}
	ll mincost(int _S, int _T) {
		S = _S, T = _T;
		if(! spfa()) return LNF;
		int u = T;
		while(u != S) {
			int i = p[u];
			e[i].ew -= f[T];
			e[i ^ 1].ew += f[T];
			u = e[i ^ 1].to;
		}
		return d[T];
	}
}
const int N = 1e5 + 5;
const int P = 998244353;
const ll LNF = 0x3f3f3f3f3f3f3f3f;
int n, m, k, a[N], b[N], id[N], pre[N];
set<int> s[N];
struct Node {
	int l, r, w;
	bool operator < (const Node & A) const {
		if(w == A.w) {
			if(l == A.l) return r < A.r;
			return l < A.l;
		}
		return w < A.w;
	}
};
void solve() {
	cin >> n >> m >> k;
	FOR(i, 1, n) cin >> a[i];
	FOR(i, 1, n) cin >> b[i];
	REP(_, m) {
		int u, v;
		cin >> u >> v;
		s[u].insert(v);
	}
	vector<Node> el, er;
	FOR(i, 1, n) id[i] = i;
	sort(id + 1, id + n + 1, [&] (int x, int y) {
		return b[x] < b[y];
	});
	FOR(i, 2, n) {
		if(b[id[i]] == b[id[i - 1]]) pre[i] = pre[i - 1];
		else pre[i] = i - 1;
	}
	FOR(i, 1, n) {
		int L = 1, R = n, pos = 0;
		while(L <= R) {
			int mid = L + R >> 1;
			if(a[i] + b[id[mid]] < P) {
				pos = mid;
				L = mid + 1;
			}
			else {
				R = mid - 1;
			}
		}
		int cnt = 0;
		REP(_, n) {
			if(cnt == k) break;
			if(pos == 0) pos = n;
			if(! s[i].count(id[pos])) {
				el.push_back({i, id[pos], (a[i] + b[id[pos]]) % P});
				cnt ++;
				pos --;
			}
			else {
				pos = pre[pos];
			}
		}
	}
	sort(id + 1, id + n + 1, [&] (int x, int y) {
		return a[x] < a[y];
	});
	FOR(i, 2, n) {
		if(a[id[i]] == a[id[i - 1]]) pre[i] = pre[i - 1];
		else pre[i] = i - 1;
	}
	FOR(i, 1, n) {
		int L = 1, R = n, pos = 0;
		while(L <= R) {
			int mid = L + R >> 1;
			if(b[i] + a[id[mid]] < P) {
				pos = mid;
				L = mid + 1;
			}
			else {
				R = mid - 1;
			}
		}
		int cnt = 0;
		REP(_, n) {
			if(cnt == k) break;
			if(pos == 0) pos = n;
			if(! s[id[pos]].count(i)) {
				er.push_back({id[pos], i, (b[i] + a[id[pos]]) % P});
				cnt ++;
				pos --;
			}
			else {
				pos = pre[pos];
			}
		}
	}
	sort(begin(el), end(el)); reverse(begin(el), end(el));
	sort(begin(er), end(er)); reverse(begin(er), end(er));
	vector<Node> E;
	int lim = k * k * 2, pl = 0, pr = 0;
	while(pl < SZ(el) && pr < SZ(er)) {
		if(el[pl] < er[pr]) {
			pr ++;
			continue;
		}
		if(er[pr] < el[pl]) {
			pl ++;
			continue;
		}
		E.push_back(el[pl]);
		pl ++; pr ++;
		if(SZ(E) == lim) break;
	}
	map<int, int> L, R; int cl = 0, cr = 0;
	for(auto h : E) {
		if(! L.count(h.l)) L[h.l] = ++ cl;
		if(! R.count(h.r)) R[h.r] = ++ cr;
	}
	Mincost :: init(cl + cr + 1);
	int S = 0, T = cl + cr + 1;
	FOR(i, 1, cl) Mincost :: add(S, i, 1, 0);
	FOR(i, 1, cr) Mincost :: add(cl + i, T, 1, 0);
	for(auto h : E) Mincost :: add(L[h.l], cl + R[h.r], 1, - h.w);
	bool ok = 1; ll res = 0;
	FOR(i, 1, k) {
		ll val = Mincost :: mincost(S, T);
		if(val == LNF) ok = 0;
		else res -= val;
		if(! ok) cout << - 1 << " ";
		else cout << res << " ";
	}
	cout << endl;
}

AGC031 E - Snuke the Phantom Thief

考虑枚举拿的宝石个数后直接上下届费用流。

ケロシの代码
const int N = 2e2 + 5;
const int M = 1e3 + 5;
const int V = 100;
const int INF = 0x3f3f3f3f;
const ll LNF = 0x3f3f3f3f3f3f3f3f;
const ll LV = 2e15;
int n, m;
int ru[N];
int px[N], py[N]; ll w[N];
int F[N][2], G[N][2];
struct Edge { int ne, to, ew; ll ct; } e[M];
int fi[N], ecnt;
int S, T, ok;
ll d[N]; bool vis[N];
int f[N], p[N];
void init() {
	ecnt = 1; ok = 1;
	memset(fi, 0, sizeof fi);
	memset(ru, 0, sizeof ru);
}
void add(int u, int v, int w, ll c) {
	e[++ ecnt] = {fi[u], v, w, c};
	fi[u] = ecnt;
	e[++ ecnt] = {fi[v], u, 0, - c};
	fi[v] = ecnt;
}
bool spfa() {
	memset(d, 0x3f, sizeof d);
	memset(vis, 0, sizeof vis);
	queue<int> q; q.push(S);
	d[S] = 0; vis[S] = 1; f[S] = INF;
	while(! q.empty()) {
		int u = q.front();
		q.pop(); vis[u] = 0;
		for(int i = fi[u]; i; i = e[i].ne) if(e[i].ew) {
			int v = e[i].to;
			if(d[v] > d[u] + e[i].ct) {
				d[v] = d[u] + e[i].ct;
				f[v] = min(f[u], e[i].ew);
				p[v] = i;
				if(! vis[v]) {
					vis[v] = 1;
					q.push(v);
				}
			}
		}
	}
	return d[T] != LNF;
}
ll mincost() {
	ll res = 0;
	while(spfa()) {
		res += d[T] * f[T];
		int u = T;
		while(u != S) {
			int i = p[u];
			e[i].ew -= f[T];
			e[i ^ 1].ew += f[T];
			u = e[i ^ 1].to;
		}
	}
	return res;
}
void add_edge(int u, int v, int l, int r) {
	if(l > r) ok = 0;
	ru[u] -= l; ru[v] += l;
	add(u, v, r - l, 0);
}
void solve() {
	cin >> n;
	FOR(i, 1, n) cin >> px[i] >> py[i] >> w[i];
	FOR(i, 1, V) REP(j, 2) F[i][j] = G[i][j] = INF;
	cin >> m;
	REP(i, m) {
		char o; int u, x;
		cin >> o >> u >> x;
		if(o == 'L') chmin(F[u][0], x);
		if(o == 'R') chmin(F[u][1], x);
		if(o == 'D') chmin(G[u][0], x);
		if(o == 'U') chmin(G[u][1], x);
	}
	ROF(i, V - 1, 1) chmin(F[i][0], F[i + 1][0]);
	ROF(i, V - 1, 1) chmin(G[i][0], G[i + 1][0]);
	FOR(i, 2, V) chmin(F[i][1], F[i - 1][1]);
	FOR(i, 2, V) chmin(G[i][1], G[i - 1][1]);
	ll ans = 0;
	S = V * 2 + 2, T = V * 2 + 3;
	FOR(i, 1, n) {
		init();
		REP(j, V) add_edge(j, j + 1, i - F[j][0], F[j + 1][1]);
		REP(j, V) add_edge(j + V + 2, j + V + 1, i - G[j][0], G[j + 1][1]);
		FOR(j, 1, n) add(px[j], py[j] + V + 1, 1, - w[j]);
		REP(j, V * 2 + 2) {
			if(ru[j] > 0) add(S, j, ru[j], 0);
			if(ru[j] < 0) add(j, T, - ru[j], 0);
		}
		add(V + 1, 0, INF, LV);
		if(! ok) continue;
		ll val = mincost();
		for(int j = fi[S]; j; j = e[j].ne) if(e[j].ew) ok = 0;
		for(int j = fi[T]; j; j = e[j].ne) if(e[j ^ 1].ew) ok = 0;
		if(! ok) continue;
		chmax(ans, LV * i - val);
	}
	cout << ans << endl;
}

[Ynoi Easy Round 2018] 星野瑠美衣

(这东西是不是应该放 Ynoi 做题笔记里(恼

考虑绝对值难搞,但是只要枚举所有的正负情况最后取个 max 即可。

这样的话两边点的贡献就可以独立,然后考虑建费用流。

接下来不难发现建出来的图是二分图,且一边只有 \(11\) 个点,所以直接模拟费用流,使用可删堆维护增广路即可。

ケロシの代码
const int N = 1e5 + 5;
const int INF = 0x3f3f3f3f;
template < typename T >
struct Set {
	priority_queue<T> p, q;
	Set() {
		p.push({- INF, 0});
	}
	void insert(T x) {
		p.push(x);
	}
	void del(T x) {
		q.push(x);
	}
	void upd() {
		while(! p.empty() && ! q.empty() && p.top() == q.top()) {
			p.pop();
			q.pop();
		}
	}
	T top() {
		upd();
		return p.top();
	}
};
int n, m;
struct Point {
	int x, y;
} a[N], b[N];
int w[N], c[N], f[N][9], g[N][9];
int pl[N], pr[N];
Set<PII> q[11][11];
int S, T; 
PII e[11][11];
int d[11], vis[11], pre[11];
void spfa() {
	REP(i, 11) d[i] = - INF;
	REP(i, 11) vis[i] = 0;
	queue<int> q; q.push(S);
	d[S] = 0; vis[S] = 1;
	while(! q.empty()) {
		int u = q.front();
		q.pop(); vis[u] = 0;
		REP(v, 11) if(u != v) {
			int w = FI(e[u][v]);
			if(d[v] < d[u] + w) {
				d[v] = d[u] + w;
				pre[v] = u;
				if(! vis[v]) {
					vis[v] = 1;
					q.push(v);
				}
			}
		}
	}
}
void solve() {
	cin >> n >> m;
	FOR(i, 1, n) cin >> a[i].x >> a[i].y;
	FOR(i, 1, m) cin >> b[i].x >> b[i].y >> w[i];
	FOR(i, 1, n) {
		int j = i % n + 1;
		c[i] = abs(a[i].x - a[j].x) + abs(a[i].y - a[j].y);
		vector<int> x {
			abs(a[i].x - a[j].x),
			a[i].x + a[j].x,
			- a[i].x - a[j].x
		};
		vector<int> y {
			abs(a[i].y - a[j].y),
			a[i].y + a[j].y,
			- a[i].y - a[j].y
		};
		REP(j, 9) f[i][j] = x[j / 3] + y[j % 3];
	}
	FOR(i, 1, m) {
		vector<int> x {
			0,
			- b[i].x * 2,
			b[i].x * 2
		};
		vector<int> y {
			0,
			- b[i].y * 2,
			b[i].y * 2
		};
		REP(j, 9) g[i][j] = x[j / 3] + y[j % 3];
	}
	S = 9, T = 10;
	ll res = 0;
	FOR(i, 1, n) res += c[i];
	FOR(i, 1, m) REP(j, 9) q[S][j].insert({g[i][j] + w[i], i});
	FOR(i, 1, n) REP(j, 9) q[j][T].insert({f[i][j] - c[i], i + m});
	REP(_, n) {
		REP(i, 11) REP(j, 11) e[i][j] = q[i][j].top();
		spfa();
		res += d[T];
		int u = T;
		while(u != S) {
			int v = pre[u];
			int i = SE(e[v][u]);
			if(v == S) {
				REP(j, 9) q[S][j].del({g[i][j] + w[i], i});
				pl[i] = u;
				REP(j, 9) if(j != pl[i]) q[pl[i]][j].insert({g[i][j] - g[i][pl[i]], i});
			}
			else if(u == T) {
				i -= m;
				REP(j, 9) q[j][T].del({f[i][j] - c[i], i + m});
				pr[i] = v;
				REP(j, 9) if(j != pr[i]) q[j][pr[i]].insert({f[i][j] - f[i][pr[i]], i + m});
			}
			else if(i <= m) {
				REP(j, 9) if(j != pl[i]) q[pl[i]][j].del({g[i][j] - g[i][pl[i]], i});
				pl[i] = u;
				REP(j, 9) if(j != pl[i]) q[pl[i]][j].insert({g[i][j] - g[i][pl[i]], i});
			}
			else {
				i -= m;
				REP(j, 9) if(j != pr[i]) q[j][pr[i]].del({f[i][j] - f[i][pr[i]], i + m});
				pr[i] = v;
				REP(j, 9) if(j != pr[i]) q[j][pr[i]].insert({f[i][j] - f[i][pr[i]], i + m});
			}
			u = v;
		}
		cout << res << " ";
	}
	cout << endl;
}

KAIST 2019 J - Jealous Teachers

神秘题。

性质一:学生的一个子集 \(S\) 相邻的老师个数一定大于 \(|S|\)

性质二:前 \(n-1\) 个学生和前 \(n-1\) 的老师存在完美匹配。

不难发现只需使一个学生向自己匹配的老师和另一个老师发花,且形成了一个树状物。

ケロシの代码
const int N = 2e5 + 5;
const int M = 1e6 + 5;
const int INF = 0x3f3f3f3f;
int n, m, p[N], b[N];
vector<int> g[N], t[N];
struct Edge { int ne, to, ew; } e[M];
int fi[N], ecnt;
int S, T, d[N], c[N];
set<int> F[N];
int fu[N], fv[N];
map<PII, int> ans;
int dp[N];
void init() {
	ecnt = 1;
	memset(fi, 0, sizeof fi);
}
void add(int u, int v, int w) {
	e[++ ecnt] = {fi[u], v, w};
	fi[u] = ecnt;
	e[++ ecnt] = {fi[v], u, 0};
	fi[v] = ecnt;
}
bool bfs() {
	memset(d, 0x3f, sizeof d);
	queue<int> q;
	d[S] = 0; q.push(S);
	while(! q.empty()) {
		int u = q.front();
		q.pop();
		for(int i = fi[u]; i; i = e[i].ne) if(e[i].ew) {
			int v = e[i].to;
			if(d[v] == INF) {
				d[v] = d[u] + 1;
				q.push(v);
			}
		}
	}
	return d[T] != INF;
}
int dfs(int u, int w) {
	if(u == T || ! w) return w;
	int res = 0;
	for(int & i = c[u]; i; i = e[i].ne) if(e[i].ew) {
		int v = e[i].to;
		if(d[v] != d[u] + 1) continue;
		int val = dfs(v, min(w, e[i].ew));
		if(! val) continue;
		e[i].ew -= val;
		e[i ^ 1].ew += val;
		w -= val;
		res += val;
		if(! w) return res;
	}
	return res;
}
int dinic(int _S, int _T) {
	S = _S, T = _T;
	int res = 0;
	while(bfs()) {
		memcpy(c, fi, sizeof c);
		res += dfs(S, INF);
	}
	return res;
}
void dfs(int u) {
	int res = n - 1;
	for(int v : t[u]) {
		dfs(v);
		res -= dp[v];
		ans[{b[v], u}] = dp[v];
	}
	ans[{b[u], u}] = res;
	dp[u] = n - res;
}
void solve() {
	cin >> n >> m;
	init();
	int S = 0, T = n * 2;
	FOR(i, 1, n - 1) add(S, i, 1);
	FOR(i, 1, n - 1) add(i + n - 1, T, 1);
	FOR(i, 1, m) {
		int u, v;
		cin >> u >> v;
		fu[i] = u; fv[i] = v;
		g[u].push_back(v);
		if(u < n && v < n) add(u, v + n - 1, 1);
	}
	if(dinic(S, T) != n - 1) {
		cout << - 1 << endl;
		return;
	}
	FOR(u, 1, n - 1) {
		for(int i = fi[u]; i; i = e[i].ne) if(e[i ^ 1].ew) {
			int v = e[i].to;
			if(v == S) continue;
			p[u] = v - n + 1;
			break;
		}
	}
	FOR(i, 1, n - 1) b[p[i]] = i;
	FOR(u, 1, n - 1) for(int v : g[u]) if(v != p[u]) F[v].insert(u);
	queue<int> q;
	q.push(n);
	REP(_, n - 1) {
		int pos = - 1;
		while(! q.empty()) {
			int u = q.front();
			if(F[u].empty()) {
				q.pop();
				continue;
			}
			pos = * F[u].begin();
			for(int v : g[pos]) if(v != p[pos]) F[v].erase(pos);
			break;
		}
		if(pos == - 1) {
			cout << - 1 << endl;
			return;
		}
		t[q.front()].push_back(p[pos]);
		q.push(p[pos]);
	}
	dfs(n);
	FOR(i, 1, m) cout << ans[{fu[i], fv[i]}] << endl;
}
posted @ 2025-05-21 08:24  KevinLikesCoding  阅读(60)  评论(0)    收藏  举报