山东二轮省集总结
时间: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)\):
- 若 \(a_i \leq b_i\),可以视作图总有这条边权为 \(b_i\),而我们可以用 \(1\) 的花费添加 \(a_i\) 这条边。此时加入边 \((u_i,v_i,b_i,0)\) 和 \((u_i,v_i,a_i,1)\)。
- 若 \(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_map 或 map 存储即可。转移是容易的。
注意为了不重复加入本质相同的 \(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;
}

浙公网安备 33010602011771号