Loading

Luogu7417 [USACO21FEB] Minimizing Edges P 做题记录

很普通但是很强大的贪心题。link

\(dis_{u, 0/1}\) 表示 \(1\to u\) 的最短奇 / 偶路径长度,原题可以转化为 \(G'\)\(G\)\(dis_{1\dots n, 0 / 1}\) 不变,求 \(G'\) 的最小边数。

先 BFS 求出 \(dis_{u, 0 / 1}\)。设 \(x = \min(dis_{u, 0}, dis_{u, 1}), y = \max(dis_{u, 0}, dis_{u, 1})\),那么可以将 \(u\) 视为二维平面上的点 \((x, y)\)

观察连边合法的条件。对于一个点 \((x, y)\),它合法当且仅当满足以下条件之一:

  • \((x - 1, y - 1)\) 有连边。

  • 同时与 \((x - 1, y + 1)\)\((x + 1, y - 1)\) 有连边。

  • \(y = x + 1\),这些点自连。

我们按照 \(x + y\) 从小到大一层一层考虑。第一个条件相当于和上面一层对应的点连边,第二个条件相当于和相邻两个点连边。

\((x, x + 1)\) 可以自连。具体的,\(k\) 个点 \((x, x + 1)\) 通过两两之间连边可以自行合法化,代价为 \(\lceil \frac k2 \rceil\)

\((x, x + 1)\) 的条件特殊考虑:必须与上一层的点 或者 \((x - 1, x + 2)\) 连边。若与 \((x - 1, x + 2)\) 连边,还需要自连合法。

所以对于每一层,可以按照 \(x\) 从小到大的顺序处理每个点。为了方便,令可以与上一层连边的点为黑点,否则为白点。

白点必须和相邻两个点连边,所以我们在处理的时候,记录一下上一个点传下来的连边需求数量。

倘若遇到黑点,我们可以直接终结连边需求。但是我们有贪心策略:连边需求能往后传就往后传,原因:

  • 若直接终结,和往后传的代价相同。

  • 若下一个点是白点,也会产生连边需求,终结无效。

  • 到了边界点 \((x, x + 1)\),若有连边需求,可以省掉其与上一层的点的连边代价,改为每个点 \(\frac 12\) 代价(注意其与相邻点连边的代价不是算在自己,而是算在那个相邻点处的)。

时间复杂度 \(\mathcal O(n + m)\)


这道题的第一步是数形结合,将图上问题转化为平面图上的问题。

然后需要观察模型,将每个点的条件转化为严格处于平面图上的条件。这里我一开始想到了和上一层连边,但是没有注意到若不与 \((x - 1, y - 1)\) 连边则必须与 \((x - 1, y + 1)\)。换言之,我认为其可以与纵坐标 \(> y + 1\) 的点连边。

如果观察到了这个更严格的条件,那么就很显然可以按照 \(x + y\) 来分层,最后贪心。

贪心的时候细节也有点多,写代码要理清思路。


点击查看代码
#include <bits/stdc++.h>
namespace Initial {
	#define ll long long
	#define ull unsigned ll
	#define fi first
	#define se second
	#define mkp make_pair
	#define pir pair <ll, ll>
	#define pb push_back
	#define i128 __int128
	using namespace std;
	const ll maxn = 2e5 + 10, inf = 1e9, mod = 998244353, iv = mod - mod / 2;
	ll power(ll a, ll b = mod - 2, ll p = mod) {
		ll s = 1;
		while(b) {
			if(b & 1) s = 1ll * s * a %p;
			a = 1ll * a * a %p, b >>= 1;
		} return s;
	}
	template <class T>
	const inline ll pls(const T x, const T y) { return x + y >= mod? x + y - mod : x + y; }
	template <class T>
	const inline void add(T &x, const T y) { x = x + y >= mod? x + y - mod : x + y; }
	template <class T>
	const inline void chkmax(T &x, const T y) { x = x < y? y : x; }
	template <class T>
	const inline void chkmin(T &x, const T y) { x = x < y? x : y; }
} using namespace Initial;

namespace Read {
	char buf[1 << 22], *p1, *p2;
	// #define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, (1 << 22) - 10, stdin), p1 == p2)? EOF : *p1++)
	template <class T>
	const inline void rd(T &x) {
		char ch; bool neg = 0;
		while(!isdigit(ch = getchar()))
			if(ch == '-') neg = 1;
		x = ch - '0';
		while(isdigit(ch = getchar()))
			x = (x << 1) + (x << 3) + ch - '0';
		if(neg) x = -x;
	}
} using Read::rd;

ll t, n, m, dis[maxn][2], q[maxn][2], l, r;
vector <ll> to[maxn], vec[maxn << 1];
ll ans, c[maxn], bin[maxn]; bool vis[maxn];

void solve() {
	rd(n), rd(m); ans = 0;
	for(ll i = 1; i <= n; i++) to[i].clear();
	for(ll i = 0; i <= 3 * n; i++) vec[i].clear();
	for(ll i = 1; i <= m; i++) {
		ll u, v; rd(u), rd(v);
		to[u].pb(v), to[v].pb(u);
	} q[l = r = 1][0] = 1, q[1][1] = 0;
	for(ll i = 1; i <= n; i++) dis[i][0] = dis[i][1] = -1;
	dis[1][0] = 0;
	while(l <= r) {
		ll u = q[l][0], z = q[l++][1];
		for(ll v: to[u])
			if(dis[v][z ^ 1] == -1) {
				dis[v][z ^ 1] = dis[u][z] + 1;
				q[++r][0] = v, q[r][1] = z ^ 1;
			}
	}
	if(dis[1][1] == -1) return printf("%lld\n", n - 1), void();
	for(ll i = 1; i <= n; i++)
		vec[dis[i][0] + dis[i][1]].pb(min(dis[i][0], dis[i][1]));
	for(ll d = 1; d <= 3 * n - 2; d += 2) {
		if(vec[d].empty()) continue;
		if(d > 1)
			for(ll x: vec[d - 2]) vis[x] = true;
		sort(vec[d].begin(), vec[d].end());
		for(ll x: vec[d]) ++bin[x];
		vec[d].erase(unique(vec[d].begin(), vec[d].end()), vec[d].end());
		for(ll x: vec[d]) {
			ll y = d - x;
			if(x == 0) {
				if(y == 1) ++ans;
				continue;
			}
			if(y == x + 1) {
				ll tmp = min(bin[x], c[x - 1]);
				if(vis[x - 1]) ans += c[x - 1] + bin[x] - tmp + (tmp + 1) / 2;
				else ans += max(bin[x], c[x - 1]) + (bin[x] + 1) / 2; 
			} else if(vis[x - 1]) {
				c[x] = min(c[x - 1], bin[x]);
				if(bin[x + 1]) ans += max(c[x - 1], bin[x]);
				else ans += c[x - 1] + bin[x];
			} else {
				ans += max(bin[x], c[x - 1]);
				c[x] = bin[x];
			}
		}
		for(ll x: vec[d]) c[x] = bin[x] = 0;
		if(d > 1)
			for(ll x: vec[d - 2]) vis[x] = false;
	} printf("%lld\n", ans);
}

int main() {
	rd(t); while(t--) solve();
	return 0;
}
posted @ 2025-02-13 11:04  Sktn0089  阅读(43)  评论(0)    收藏  举报