山东二轮省集总结

时间:2024.5.1 ~ 2024.5.9。

Day1

一个题没补,再见。

Day2

Problem A. 最小生成树

题意:给定一个 \(n\) 个点 \(m\) 条边的带权无向连通图,每条边 \(i\) 有两个权重 \(a_i, b_i\)

对于所有 \(0 \leq k \leq m\),求出如果恰好选择 \(k\) 条边的权值为 \(a_i\),其余 \(m-k\) 条边的权值为 \(b_i\) 时,最小生成树权值的最大值是多少。

范围:\(n \leq 9\)\(m \leq 100\)\(1 \leq a_i, b_i \leq 10^8\),可能有重边,但是没有自环。

解法:

很厉害一个题,赛时没想出来。

考虑 Kruskal 的过程,将边权从小到大排序,然后依次加入每条边,并且维护并查集。

考虑分类讨论,对于每条边 \((u_i,v_i,a_i,b_i)\)

  1. \(a_i \leq b_i\),可以视作图总有这条边权为 \(b_i\),而我们可以用 \(1\) 的花费添加 \(a_i\) 这条边。此时加入边 \((u_i,v_i,b_i,0)\)\((u_i,v_i,a_i,1)\)
  2. \(a_i > b_i\),可以视作图总有边权为 \(a_i\) 的这条边,可以花费 \(-1\) 的代价添加 \(b_i\) 这条边。此时加入边 \((u_i,v_i,a_i,0)\)\((u_i,v_i,b_i,-1)\)

将所有加入的边按照边权排序,考虑状态压缩 DP,\(f_{i,S}\) 表示我考虑了前 \(i\) 条边,当前并查集的状态为 \(S\)\(S\) 是一个 vector,这里使用 unordered_mapmap 存储即可。转移是容易的。

注意为了不重复加入本质相同的 \(S\),考虑将所有点在并查集中的本质根作为 \(fa\),即可。复杂度 \(O(m^2\operatorname{Bell}(n))\),其中 \(\operatorname{Bell}(9)=21147\)

代码:

#pragma GCC optimize("-Ofast,fast-math,-inline")
#include <bits/stdc++.h>
using namespace std;
//#define int long long

using ll = long long;

const int N = 5e5 + 5, MOD = 1e9 + 7, HSMOD = 1610612741, HSMOD2 = 998244353; // Remember to change

int n, m, q, t;

int x[N], y[N], a[N], b[N];

struct Edge
{
	int u, v, w, o;
	Edge() = default;
	Edge(int u, int v, int w, int o): u(u), v(v), w(w), o(o){}
};

struct State
{
	int y;
	vector<int> fa;
	State() = default;
	State(int y, vector<int> fa): y(y), fa(fa){}
	bool operator<(const State& sx) const
	{
		return ((y != sx.y ? y < sx.y : fa < sx.fa));
	}
};

map<State, int> dp[205];
int ans[205];

int main()
{
	//freopen("*.in", "r", stdin);
	//freopen("*.out", "w", stdout);
	//ios::sync_with_stdio(0), cin.tie(0);
	cin >> n >> m;
	vector<Edge> ve;
	int c = 0;
	for (int i = 1; i <= m; i++)
	{
		cin >> x[i] >> y[i] >> a[i] >> b[i];
		if (a[i] <= b[i])
		{
			ve.emplace_back(Edge(x[i], y[i], b[i], 0));
			ve.emplace_back(Edge(x[i], y[i], a[i], 1));
		}
		else
		{
			ve.emplace_back(Edge(x[i], y[i], a[i], 0));
			ve.emplace_back(Edge(x[i], y[i], b[i], -1));
			c++;
		}
	}
	sort(ve.begin(), ve.end(), [&](const auto& x, const auto& y){return (x.w != y.w ? x.w < y.w : (!(y.o != 0 && x.o == 0)));});
	vector<int> res;
	res.resize(n + 1);
	for (int i= 1; i <= n; i++) res[i] = i;
	dp[0][State(0, res)] = 0;
	for (int i = 0; i < 2 * m; i++)
	{
		auto [u, v, w, o] = ve[i];
		for (auto &x : dp[i])
		{
			auto &[y, fa] = x.first;
			int val = x.second;
			// case1: skip this edge
			// case2: merge
			if (o != 0)
			{
				if (fa[u] != fa[v])
				{
					// 选择这条边 
					int ru = u, rv = v;
					u = fa[u], v = fa[v];
					vector<int> nf;
					nf = fa;
					if (u < v)
					{
						for (int x = 1; x <= n; x++) if (nf[x] == u) nf[x] = v;
					}
					else 
					{
						for (int x = 1; x <= n; x++) if (nf[x] == v) nf[x] = u;
					}
					dp[i + 1][State(y + o, nf)] = max(dp[i + 1][State(y + o, nf)], val + w);
					u = ru, v = rv;
				}
				else dp[i + 1][State(y + o, fa)] = max(dp[i + 1][State(y + o, fa)], val);
				// 不选择 
				dp[i + 1][State(y, fa)] = max(dp[i + 1][State(y, fa)], val);
			}
			else
			{
				if (fa[u] != fa[v]) // 之前没选 
				{
					int ru = u, rv = v;
					u = fa[u], v = fa[v];
					vector<int> nf;
					nf = fa;
					if (u < v)
					{
						for (int x = 1; x <= n; x++) if (nf[x] == u) nf[x] = v;
					}
					else 
					{
						for (int x = 1; x <= n; x++) if (nf[x] == v) nf[x] = u;
					}
					dp[i + 1][State(y, nf)] = max(dp[i + 1][State(y, nf)], val + w);
					u = ru, v = rv;
					//dp[i + 1][State(y, fa)] = max(dp[i + 1][State(y, fa)], val);
				}
				else
				{
					dp[i + 1][State(y, fa)] = max(dp[i + 1][State(y, fa)], val);
				}
			}
		}
	}
	for (auto &j : dp[2 * m])
	{
		int nc = j.first.y + c;
		set<int> s;
		for (auto &x : j.first.fa) s.insert(x);
		s.erase(0);
		if (s.size() == 1) ans[nc] = max(ans[nc], j.second);
	}
	for (int i = 0; i <= m; i++)
	{
		cout << ans[i] << "\n";
	}
	return 0;
}
posted @ 2024-05-05 21:05  HappyBobb  阅读(3)  评论(0)    收藏  举报