再学二分图

增广路:从一个未匹配点出发,交替经过非匹配边、匹配边的路径,到达另一个未匹配点, 这一条路径叫做增广路。

以下省略绝对值符号,表示“集合大小”

  1. 最小点覆盖集 = 最大匹配数
  2. 最大点独立集 = 顶点总数 - 最大匹配数
  3. 最小边覆盖集 = 顶点总数 - 最大匹配数(无孤立点)
  4. DAG 最小路径覆盖 = 顶点总数 - 最大匹配数

二分图最少点边覆盖

对于一个已知的最大匹配,我们考虑从他出发求一组最小点覆盖集的方案

法 1

从左部所有非匹配点出发,做增广路,标记经过的所有点

我们选取左部点中所有未被标记点,与右部点中所有被标记的点,这就是一种方案

证明考虑增广路的性质,按两个端点是否匹配,把边分成 4 类进行讨论

法 2

考虑最小割:

  • 新增源汇 \(s,t\)
  • 二分图的边保留,边权为 \(+\infty\)
  • \(s\) 向左部点连边权为 \(1\) 的边
  • 右部点向 \(t\) 连边权为 \(1\) 的边
  • 若一个点所连的边权为 \(1\) 的边被割,则该点要选

二分图最大独立集

方案:所有点扔去最小点覆盖的点

不可重最少路径覆盖问题

就是 DAG 的最小链覆盖

把原图每个点拆为左部点和右部点,若原图有一条有向边 \(u\to v\),则在左部点中的点 \(u\) 连向右部点中的点 \(v\),最大匹配指的是这个二分图的最大匹配

这里选中一条边,在原图中相当于合并两条路径

最多无路径点集

对第一问做传递闭包,就变成了第二问

对于第二问,由 Dilworth 定理,该图的最大独立集等于最小链覆盖,于是答案是好求的

最难的是如何给出方案:

  • 先转二分图,在二分图上求出最大独立集:
  • 新增源汇 \(s,t\),每个点 \(u\) 拆为 \(p(u),q(u)\) 两个点
  • \(s\) 向所有 \(p(u)\) 连边,所有 \(q(u)\)\(t\) 连边
  • 对于原图边 \((u,v)\),连边 \(p(u)\to q(v)\)
  • \(p(u),q(u)\) 都在最大独立集里,则 \(u\) 在原图最大独立集中

证明:设 \(k\) 为最大匹配数,根据简单的抽屉原理,我们选择的点数 \(\ge n-k\),而已知最小链覆盖 \(=n-k\) 等于最大独立集大小,所以对了。

棋盘博弈

删掉障碍点,对棋盘黑白染色得到一个二分图,二分图博弈经典结论:

  • 规则:两人轮流沿边行动,不允许重复访问节点,无法移动者输。
  • 结论:起点 \(u\) 是先手必胜的当且仅当 \(u\) 在所有最大匹配上。(自己推根本推不出来 QAQ)

证明:

  • 必要性:若存在最大匹配不含 \(u\),先手每次要么走到匹配点上(不然找到了一条增广路),要么无路可走,后手只需每次走到与当前点相匹配的点即可
  • 充分性:当 \(u\) 在所有最大匹配上时,我们任取最大匹配,先手将 \(u\) 移动到匹配点,后手每次要么走到匹配点上(不然找到了一个不含起点的最大匹配),要么无路可走

判断方法:

  • 删掉起点 \(u\),再跑一遍最大匹配,若大小不变,则 \(u\) 是非必须点,则先手必败,否则先手必胜
  • 跑最大流,若 \(u\) 在最大匹配中,且残量网络中 \(S\) 无法到达 \(u\),则 \(u\) 是必须点,先手必胜,否则先手必败

第二种方法,正确性在于此时无法退流得到新的不含 \(u\) 的最大流方案

半最大独立集

一个一个连通块的处理

若不是二分图,即存在奇环,考虑全赋 0.5 是最优的

否则,求出最小点覆盖的点集,令该点集的点为 1,其余点为 0 是最优的

打副本

Gym 104090H

第二问的修改是不影响第一问的,先算第一问

首先一组内包含 4 种人:\(\texttt{D}\) 职业、\(\texttt{S}\) 职业、\(\texttt{B}\) 职业、\(\texttt{D}\)\(\texttt{S}\) 灵活选择的职业

首先二分答案 \(k\),然后可以最大流:

  • 建点 \(s,t\),7 个左部点 \(p_{\texttt{D}},p_{\texttt{S}},p_{\texttt{B}},p_{\texttt{DS}},p_{\texttt{DB}},p_{\texttt{SB}},p_{\texttt{DSB}}\) 分别表示 7 种人,4 个右部点 \(q_{\texttt{D}},q_{\texttt{S}},q_{\texttt{B}},q_{\texttt{D/S}}\) 表示担任组内哪个职业
  • \(\forall u\in\) 左部点,设有 \(x\) 个人是 \(u\) 类人,\(s\to u\) 连容量为 \(x\) 的边
  • \(\forall v\in\) 右部点,\(v\to t\) 连容量为 \(k\) 的边,表示需要这么多人
  • \(\forall u\in\) 左部点,若 \(u\) 种类包含 \(\texttt{D}\),连边 \(u\to q_{\texttt{D}},u\to q_{\texttt{D/S}}\),若包含 \(\texttt{B}\),连边 \(u\to q_{\texttt{B}}\),以此类推,容量都为 \(+\infty\)
  • 跑最大流,若跑满 \(4k\) 个人则有解;
  • 或者使用霍尔定理,可以不用二分 \(O(2^4)\) 解决:\(Ans=\min_S\left\lfloor\frac{|N(S)|}{|S|}\right\rfloor\)

然后考虑第二问:

首先按成本对选手排序,最初这些选手全被选,每次选择可能可以不选的成本最高的选手,尝试取消他:

  • 若不可能用剩下的人组成足够的小队,则该选手必须被选
  • 若可能用剩下的人组成足够的小队,那就取消他

这样,设 \(d\) 类选手选了 \(\text{sz}(d)\) 个,其一定选的是成本最小的前 \(\text{sz}(d)\)

最后考虑如何进行修改 \((x,y)\)

  • 维护 7 个 multiset 存 7 种类型的未选人员的成本集合,再维护 7 个 multiset 存 7 种类型的已选人员的成本集合
  • 若最优策略产生变化,可能有这几种情况:
    • \(x\) 没被选,而修改后会 \(\le\) 已选的最大值,可以尝试用他去替换某个类型已选的最大值
    • \(x\) 已被选,而修改后会 \(>\) 已选的最大值,可以尝试用某个类型未选的最小值去替换他

\(O(q\log n)\)

矩阵归零

设第 \(i\) 行减少量为 \(A_i\),第 \(i\) 列减少量为 \(B_i\)(可以为负)

那么最终操作数为 \(\sum c_{i,j}-(n-1)(\sum A_i+\sum B_i)\),注意单点只能减,所以要求 \(A_i+B_j\le c_{i,j}=|a_i-b_j|\) 情况下 \(\sum A_i+\sum B_i\) 最大。

\(A_i,B_i\) 取反,问题变成要求 \(A_i+B_j\ge-c_{i,j}\) 情况下 \(\sum A_i+\sum B_i\) 最小。

火速写出规划形式:

image1

光速写出对偶问题:

image2

直接赋予实际意义:\(x_{u,v}\) 表示一条左部点 \(u\) 连向右部点 \(v\) 的边是否被选入匹配,\(-c_{u,v}\) 表示该边的权值,求的是最大权完美匹配

很对啊!

问题变成让 \(a,b\) 两两匹配,使得差的绝对值之和最小

可以直接贪心:\(a,b\) 分别排序,然后对应位置匹配即可。

配对 1

LOJ 6062

\(b_i\gets h-b_i\),再把 \(b\) 从大到小排序,单次判可以让 \(a\) 从大到小排序,若每个 \(a_i\ge b_i\) 则满足。

进行一步充要的转化:若对于每个 \(i\) 都有 \(\ge i\)\(a_j\) 不小于 \(b_i\),则满足

考虑直接线段树维护每个值有多少 \(a_i\) 大于等于他:

先给 \(a,b\) 离散化,初始 \(b_i\) 位置设为 \(-i\),每加入一个 \(a_i\) 就令 \([1..a_i]\) 加一,问全局最小值是否 \(\ge 0\) 即可

若有多个相同的 \(b_i\),取其中最大的 \(i\) 减就行了

配对 2

膜 include_c,他跟我讲的怎么做

首先转化条件,相当于左边 \(i\) 向右边 \((a_i-b_i)\bmod n\)\((a_i+b_i)\bmod n\) 连边

也就是每个女生度数 \(\le 2\)(当时就做到这里。。。)

于是把每个男生看成一个点,每个女生看成一条边,连接她能选择的两个男生

这题就相当于,你要给这些边定向,要求最终每个点的入度 \(\le 1\),且收益最大

发现如果要有解,一个连通块要么长成一棵树(一个点入度为 \(0\)),要么长成一棵基环树(没有点入度为 \(0\)

于是讨论:

基环树:一定是外向地定向,所以只有两种情况,要么环上顺时针定向,要么环上逆时针定向,这个非常容易动态维护。

树:这个比较复杂,首先答案一定是选一个根,然后向外一层层定向

所以考虑换根,记 \(f(u)\)\(u\) 点答案,你发现一条边的修改,可以看成对 \(f\) 的区间加

所以用一个线段树维护

\(O(n\log n)\),有点难写

idea:一个点度数 \(\le 2\) 时可以尝试化点为边

Code
#include <bits/stdc++.h>
using namespace std;
#define rep(i, j, k) for (int i = (j); i <= (k); ++i)
#define reo(i, j, k) for (int i = (j); i >= (k); --i)
typedef long long ll;
const int N = 1e6 + 10;
ll ans, a[N], b[N];
int n, m, T, q;
struct Edge {
	int v, w, id;
};
struct edge {
	int u, v, w;
} ee[N];
vector<Edge> G[N];
int bro[N];

int vis[N];
int tp, nodestk[N];
int stp, nowstk[N], estp, nestk[N];
int tot, cir[N];
int etot, cire[N];
int oncirn[N];

int tim1, dfn1[N];

int dep[N];

void DFS1(int u, int fa, int fe = 0) {
	dfn1[u] = ++tim1, vis[u] = 1, nodestk[++tp] = u, nowstk[++stp] = u;
	for (auto e : G[u]) {
		int v = e.v;
		if (!vis[v]) nestk[++estp] = e.id, DFS1(v, u, e.id), --estp;
		else if ((v != fa || e.id != bro[fe]) && dfn1[v] <= dfn1[u]) {
			int tmp = stp;
			while (nowstk[tmp] != v) cir[++tot] = nowstk[tmp--];
			cir[++tot] = v;
			tmp = estp;
			if (u != v) {
				while (ee[nestk[tmp]].u != v) cire[++etot] = nestk[tmp--];
				cire[++etot] = nestk[tmp--];
				reverse(cire + 1, cire + etot + 1);
				cire[++etot] = e.id;
			} else {
				cire[++etot] = e.id;
			}
		}
	}
	--stp;
}

void DFS2(int u, int fa) {
	dep[u] = dep[fa] + 1;
	for (auto e : G[u]) {
		int v = e.v;
		if (v != fa && !oncirn[v]) {
			ans += e.w, DFS2(v, u);
		}
	}
}

int cirtot, zheng[N], fan[N];
int oncire[N];

int tim, dfn[N], sz[N], ontree[N];
ll all;
void DFS3(int u, int fa) {
	dfn[u] = ++tim, sz[u] = 1, dep[u] = dep[fa] + 1;
	for (auto e : G[u]) {
		int v = e.v;
		if (v != fa) {
			DFS3(v, u), sz[u] += sz[v];
			all += e.w;
		}
	}
}

ll F[N];
array<int, 2> arrange[N];
void DFS4(int u, int fa, ll res) {
	F[dfn[u]] = res;
	for (auto e : G[u]) {
		int v = e.v;
		if (v != fa) {
			DFS4(v, u, res - e.w + ee[bro[e.id]].w);
		}
	}
}

void Solve(int rt) {
	tp = tot = stp = etot = estp = 0;
	DFS1(rt, 0);
	vector<int> edges;
	rep(i, 1, tp) 
		for (auto e : G[nodestk[i]]) edges.push_back(e.id);
	sort(edges.begin(), edges.end());
	if ((int)edges.size() != (tp - 1) * 2) {
		rep(i, 1, tot) oncirn[cir[i]] = 1;
		rep(i, 1, tot) DFS2(cir[i], 0);
		++cirtot;
		rep(i, 1, etot) {
			int e = cire[i];
			zheng[cirtot] += ee[e].w;
			fan[cirtot] += ee[bro[e]].w;
			oncire[e] = cirtot;
			oncire[bro[e]] = -cirtot;
		}
		ans += max(zheng[cirtot], fan[cirtot]);
	} else {
		int L = tim + 1;
		all = 0;
		DFS3(rt, 0), DFS4(rt, 0, all);
		int R = tim;
		ll res = 0;
		rep(i, 1, tp) {
			int u = nodestk[i];
			res = max(res, F[dfn[u]]);
			arrange[u] = {L, R};
			ontree[u] = 1;
		}
		ans += res;
	}
}

#define lc (u << 1)
#define rc ((u << 1) | 1)
#define mid ((l + r) >> 1)

ll add[N << 2], mx[N << 2];
void up(int u) {
	mx[u] = max(mx[lc], mx[rc]);
}
void Add(int u, ll v) {
	mx[u] += v, add[u] += v;
}
void down(int u) {
	if (add[u]) Add(lc, add[u]), Add(rc, add[u]), add[u] = 0;
}
void build(int u, int l, int r) {
	if (l > r) return;
	if (l == r) return (void)(mx[u] = F[l]);
	build(lc, l, mid), build(rc, mid + 1, r), up(u);
}
void upd(int u, int l, int r, int x, int y, ll v) {
	if (y < l || r < x || x > y) return;
	if (x <= l && r <= y) return Add(u, v);
	down(u), upd(lc, l, mid, x, y, v), upd(rc, mid + 1, r, x, y, v), up(u);
}
ll qry(int u, int l, int r, int x, int y) {
	if (y < l || r < x || x > y) return (ll)-1e18;
	if (x <= l && r <= y) return mx[u];
	return down(u), max(qry(lc, l, mid, x, y), qry(rc, mid + 1, r, x, y));
}

#undef lc
#undef rc
#undef mid

void init() {
	rep(i, 1, n) 
		if (!vis[i]) 
			Solve(i);
	build(1, 1, tim);
}

int main() {
	ios::sync_with_stdio(false), cin.tie(nullptr);
	cin >> m >> n >> T;
	rep(i, 1, m) cin >> a[i];
	rep(i, 1, m) cin >> b[i];
	int tmp = 0;
	rep(i, 1, m) {
		int u = (a[i] - b[i] + n) % n + 1, v = (a[i] + b[i]) % n + 1, w;
		if (u < v) swap(u, v);
		cin >> w, ++tmp;
		G[u].push_back({v, w, tmp}), ee[tmp] = {u, v, w}, bro[tmp] = tmp;
		if (u != v) cin >> w, ++tmp, G[v].push_back({u, w, tmp}), ee[tmp] = {v, u, w}, bro[tmp - 1] = tmp, bro[tmp] = tmp - 1;
	}
	init();
	cout << ans << '\n';
	cin >> q;
	while (q--) {
		int x, v;
		cin >> x >> v;
		x -= ans * T, v -= ans * T;
		if (oncire[x] != 0) {
			int cirid = abs(oncire[x]);
			int delta = v - ee[x].w;
			ans -= max(zheng[cirid], fan[cirid]);
			if (ee[x].u != ee[x].v) {
				(oncire[x] > 0 ? zheng[cirid] : fan[cirid]) += delta;
			} else {
				zheng[cirid] += delta, fan[cirid] += delta;
			}
			ans += max(zheng[cirid], fan[cirid]);
			ee[x].w = v;
		}
		else if (ontree[ee[x].u]) {
			int U = ee[x].u, V = ee[x].v;
			int l = arrange[U][0], r = arrange[U][1];
			int p = dep[U] < dep[V] ? V : U;
			int delta = v - ee[x].w;
			ans -= qry(1, 1, tim, l, r);
			if (dep[U] > dep[V]) {
				upd(1, 1, tim, dfn[p], dfn[p] + sz[p] - 1, delta);
			}
			else {
				upd(1, 1, tim, l, r, delta);
				upd(1, 1, tim, dfn[p], dfn[p] + sz[p] - 1, -delta);
			}
			ans += qry(1, 1, tim, l, r);
			ee[x].w = v;
		}
		else {
			int U = ee[x].u, V = ee[x].v;
			if (dep[U] < dep[V]) {
				ans = ans - ee[x].w + v;
			}
			ee[x].w = v;
		}
		cout << ans << '\n';
	}
	return 0;
}
posted @ 2024-11-13 19:31  Laijinyi  阅读(7)  评论(0编辑  收藏  举报