做题记录 230324 // 最小生成树
为什么擦眼睛会痛
因为拭目痛い
A. Jungle Roads
http://222.180.160.110:1024/contest/3452/problem/1
纯最小生成树,比较坑的点是因为向 POJ 远程提交,所以没办法用万能头,还只有 C++98。我悲痛!
6,GM 评价代码习惯优劣的标准是能否在 POJ 上过编译,要不猜猜为什么 CCF 把标准从 C++98 改成了 C++14?
namespace XSC062 {
const int maxn = 1e5 + 5;
const int maxm = 1e5 + 5;
struct _ {
int x, y, w;
_() {}
_(int x1, int y1, int w1) {
x = x1, y = y1, w = w1;
}
bool operator< (const _ &q) const {
return w < q.w;
}
};
char t;
int f[maxn];
std::vector<_> g;
int n, x, y, w, k, res;
inline void Init(int n) {
res = 0, g.clear();
for (int i = 1; i <= n; ++i)
f[i] = i;
return;
}
int find(int x) {
return x == f[x] ? x : f[x] = find(f[x]);
}
inline void merge(int x, int y) {
f[find(x)] = find(y);
return;
}
int main() {
scanf("%d", &n);
while (n) {
Init(n);
for (int i = 1; i < n; ++i) {
scanf("%1s %d", &t, &k);
x = t - 'A' + 1;
while (k--) {
scanf("%1s %d", &t, &w);
y = t - 'A' + 1;
g.push_back(_(x, y, w));
}
}
std::sort(g.begin(), g.end());
for (int i = 0; i < (int)g.size(); ++i) {
int x = g[i].x, y = g[i].y;
if (find(x) != find(y)) {
merge(x, y);
res += g[i].w;
}
}
printf("%d\n", res);
scanf("%d", &n);
}
return 0;
}
} // namespace XSC062
B. 野餐规划
http://222.180.160.110:1024/contest/3452/problem/2
没看 AcWing 的题解之前根本想不到啊(虽然 LHY 已经讲过两遍了),太妙了这题也!
法一
因为 \(n\le 20\),故考虑爆搜……
先不管 \(s\) 的限制,对整个图求出最小生成树。
\(s\) 对与树根(公园)相连的边的数量作出了限制(因为进入公园就不能再离开),所以我们把所有连接着根和其他节点的边遮住不看,则整个生成树被分为根和若干个本来应与根相连的子树。问题就转化为了:在各个子树中求出接到所有人的最小花费,在所有连通块中挑选 \(s\) 个与根相连。
这里给出一个引理:最小生成树中任意子树均是组成该子树的节点在原图中的最小生成树。简单来说就是最小生成树的子树都是最小生成树。而证明就是 Prim 算法的工作原理:如果这一组节点之间还有花费更小的连边方式,那么原最小生成树中这几个节点间的连边就应该选用花费更小的这种方案,与前提矛盾。
所以我们分隔出来的子树其实就是原图中这几个点的最小生成树。这意味着我们可以通过跑整个图的最小生成树,然后不连与根节点相连的边来达到目的。
对于这些子树,若他们的数量大于 \(s\),应该怎么办呢?很简单,我们可以任选若干对在原图中有连边的子树并连边,则两个子树均可以与根联通。但是因为直接与根相连肯定更优(否则如果块与块之间的边权更小,最小生成树就会选择这条边而非这个块与根相连的边),我们可以进行搜索,在连通块中选择 \(s\) 个与根相连,剩余与已和根连通的连通块连边。
namespace XSC062 {
const int inf = 1e18;
const int maxn = 1e3 + 5;
using str = std::string;
struct _ {
int x, y, w;
_() {}
_(int x1, int y1, int w1) {
x = x1, y = y1, w = w1;
}
bool operator< (const _ &q) const {
return w < q.w;
}
};
str t1, t2;
bool vis[maxn];
std::vector<_> g;
int dis[maxn][maxn];
int f[maxn], b[maxn];
std::map<str, int> t;
int n, s, x, y, w, cnt, tot, ans, res;
inline void swap(int &x, int &y) {
x ^= y ^= x ^= y;
return;
}
inline int min(int x, int y) {
return x < y ? x : y;
}
inline int max(int x, int y) {
return x > y ? x : y;
}
inline void Init(int n) {
res = 0, g.clear();
for (int i = 1; i <= n; ++i)
f[i] = i;
return;
}
int find(int x) {
return x == f[x] ? x : f[x] = find(f[x]);
}
inline void merge(int x, int y) {
f[find(x)] = find(y);
return;
}
void DFS(int x, int k, int t, int u) {
if (t >= res)
return;
if (u == tot) {
res = t;
return;
}
if (k != 0) {
for (int i = x + 1; i <= tot; ++i) {
if (vis[i])
continue;
vis[i] = 1;
DFS(i, k - 1, t + dis[i][0], u + 1);
vis[i] = 0;
}
}
else {
for (int i = 1; i <= tot; ++i) {
if (vis[i])
continue;
vis[i] = 1;
int mi = inf;
for (int j = 1; j <= tot; ++j) {
if (vis[j])
mi = min(mi, dis[i][j]);
}
DFS(-1, 0, t + mi, u + 1);
vis[i] = 0;
}
}
return;
}
int main() {
scanf("%lld", &n);
Init(n + 5);
t["Park"] = ++cnt;
for (int i = 1; i <= n; ++i) {
std::cin >> t1 >> t2;
scanf("%lld", &w);
if (!t.count(t1))
t[t1] = ++cnt;
if (!t.count(t2))
t[t2] = ++cnt;
x = t[t1], y = t[t2];
g.push_back(_(x, y, w));
}
scanf("%lld", &s);
memset(dis, 0x3f, sizeof (dis));
std::sort(g.begin(), g.end());
for (auto i : g) {
x = find(i.x), y = find(i.y);
if (y == 1)
swap(x, y);
if (x == y || (x == 1 && b[y]))
continue;
if (x == 1) {
// Connect with root
b[y] = ++tot;
dis[b[y]][0] = i.w;
res += i.w;
}
else if (b[x] && b[y]) {
dis[b[x]][b[y]] =
min(dis[b[x]][b[y]], i.w);
dis[b[y]][b[x]] = dis[b[x]][b[y]];
}
else {
f[x] = y, b[x] = b[y] = max(b[x], b[y]);
ans += i.w;
}
}
if (tot > s) {
res = inf;
DFS(0, s, 0, 0);
}
printf("Total miles driven: %lld", ans + res);
return 0;
}
} // namespace XSC062
C. Tree
http://222.180.160.110:1024/contest/3452/problem/3
我依稀记得这道题是二分。二分什么呢?
我们发现,白边多了也不行,白边少了也不行。那我们要通过什么方式来控制白边的数量呢?
orz 这个处理太神了,看自己之前的代码之前完全想不到……
—— · EOF · ——
真的什么也不剩啦 😖