0x62 最小生成树
走廊泼水节
题意:
给定一棵树,将这棵树加边,扩充为完全图,使完全图的最小生成树为原来的树,询问增加的边权值总和最小是多少
解析:
考虑 kruskal 产生最小生成树的过程:选择当前连接两个连通块边权最小的边。
所以,对于最小生成树上的边,该边一定是两个连通块之间唯一的最短边,设该边权值为 w w w,则两个连通块之间的边权值一定大于 w w w,最小为 w + 1 w+1 w+1。
设树上边连接的两个连通块的大小分别为 s i z ( x ) , s i z ( y ) siz(x),siz(y) siz(x),siz(y)。则两个连通块之间有 s i z ( x ) × s i z ( y ) siz(x)\times siz(y) siz(x)×siz(y) 条边,其中有一条是树上的边。对答案的贡献为 ( s i z ( x ) × s i z ( y ) − 1 ) × ( w + 1 ) (siz(x)\times siz(y)-1) \times (w+1) (siz(x)×siz(y)−1)×(w+1)
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define fi first
#define se second
#define debug(x) cerr << #x << ": " << (x) << endl
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
const int maxn = 1e5+10;
const int maxm = 1e5+10;
const int INF = 0x3f3f3f3f;
typedef pair<int, int> pii;
struct edge{
int u, v, w;
edge(){}
edge(int u, int v, int w) : u(u), v(v), w(w){}
bool operator < (const edge &b) const{
return w < b.w;
}
}e[maxn];
ll fa[maxn], siz[maxn];
void init(int MAX){
for(int i = 1; i <= MAX; i++){
fa[i] = i;
siz[i] = 1;
}
}
int find(int x){
return x == fa[x] ? fa[x] : fa[x] = find(fa[x]);
}
void merge(int x, int y){
int fx = find(x);
int fy = find(y);
if(fx != fy){
fa[fx] = fy;
siz[fy] += siz[fx];
}
}
int n;
void solve(){
cin >> n;
init(n);
for(int i = 1, u, v, w; i < n; i++){
cin >> u >> v >> w;
e[i] = edge(u, v, w);
}
sort(e+1, e+n);
ll ans = 0;
for(int i = 1; i < n; i++){
int u = e[i].u;
int v = e[i].v;
int w = e[i].w;
int fx = find(u);
int fy = find(v);
if(fx == fy)
continue;
ans += (siz[fx]*siz[fy]-1) * (w+1);
merge(fx, fy);
}
cout << ans << endl;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int T;
cin >> T;
while(T--)
solve();
return 0;
}
野餐规划
题意:
求图的最小生成树,节点 1 1 1 的度最大为 s s s。
解析:
首先删去节点 1 1 1,原图变成 t t t 个连通块 ( t ≥ 1 ) (t \ge 1) (t≥1)。
注意到最小生成树的子树也一定是对应子图的最小生成树。所以在每个连通块中求出最小生成树。
然后把 1 1 1 节点向每个连通块选最小的边连边,得到一棵生成树,但这棵生成树不一定最小。
对一个连通块而言,可以增加一条 1 1 1 号节点连向该连通块的边,然后删去连通块的最小生成树上的一条边,有可能会使权值和更小。
可以求出一个连通块中到 1 1 1 号节点的路径上的边权最大值。枚举 1 1 1 号节点的边 ( 1 , p ) (1,p) (1,p) ,如果不在最小生成树上,则可以加边 ( 1 , p ) (1,p) (1,p),删去边 ( u , v ) (u,v) (u,v), ( u , v ) (u,v) (u,v) 是节点 p p p 到 节点 1 1 1 路径上边权最大的边。
直到节点 1 1 1 的度为 s s s,或者加边之后边权不会变小。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define fi first
#define se second
#define mkp(a, b) make_pair((a), (b))
#define debug(x) cerr << #x << ": " << (x) << endl
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
const int maxn = 1e3+10;
const int maxm = 1e5+10;
const int INF = 0x3f3f3f3f;
typedef pair<int, int> pii;
int head[maxn], tot;
struct Edge{
int to, nxt, w;
}e[maxm << 1];
int g[maxn][maxn];
struct node{
int a, b, w;
node(){}
node(int a, int b, int w) : a(a), b(b), w(w){}
bool operator < (const node &rhs) const{
return w < rhs.w;
}
}f[maxn];
vector<node> edg;
void add(int a, int b, int c){
e[++tot].nxt = head[a];
e[tot].to = b;
e[tot].w = c;
head[a] = tot;
}
int fa[maxn];
int find(int x){
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void merge(int x, int y){
int fx = find(x);
int fy = find(y);
if(fx != fy)
fa[fx] = fy;
}
int cnt; //连通块个数
int bel[maxn]; // 哪个连通块
vector<int> block[maxn];
map<pii, bool> tree;
map<string, int> id;
int n, m, s;
void dfs(int u){
bel[u] = cnt;
block[cnt].push_back(u);
for(int i = head[u]; i; i = e[i].nxt){
int v = e[i].to;
if(!bel[v] && v != 1)
dfs(v);
}
}
int kruskal(){
sort(edg.begin(), edg.end());
int res = 0;
for(int i = 0; i < edg.size(); i++){
int fx = find(edg[i].a);
int fy = find(edg[i].b);
if(fx != fy && fx != 1 && fy != 1){
res += edg[i].w;
fa[fx] = fy;
tree[mkp(edg[i].a, edg[i].b)] = 1;
tree[mkp(edg[i].b, edg[i].a)] = 1;
}
}
return res;
}
void dp(int u, int p){
for(int i = head[u]; i; i = e[i].nxt){
int v = e[i].to;
if(v == p || !tree[mkp(u, v)])
continue;
if(f[u].w > e[i].w)
f[v] = edg[u];
else
f[v] = node(u, v, e[i].w);
dp(v, u);
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> m;
id["Park"] = ++n;
string a, b;
int c;
for(int i = 1; i <= m; i++){
cin >> a >> b >> c;
if(!id.count(a))
id[a] = ++n;
if(!id.count(b))
id[b] = ++n;
add(id[a], id[b], c);
add(id[b], id[a], c);
g[id[a]][id[b]] = g[id[b]][id[a]] = c;
edg.push_back(node(id[a], id[b], c));
}
cin >> s;
for(int i = 1; i <= n; i++)
fa[i] = i;
for(int i = 2; i <= n; i++){
if(!bel[i]){
++cnt;
dfs(i);
}
}
int ans = kruskal();
for(int i = 1; i <= cnt; i++){
int tmp = INF;
int p = -1;
for(auto u : block[i]){
if(g[1][u] != 0 && tmp > g[1][u]){
tmp = g[1][u];
p = u;
}
}
ans += tmp;
tree[mkp(1, p)] = 1;
tree[mkp(p, 1)] = 1;
}
while(s > cnt){
s--;
for(int i = 0; i <= n; i++)
f[i].w = -1;
dp(1, 0);
int tmp = -1, p = -1;
for(int i = head[1]; i; i = e[i].nxt){
int v = e[i].to;
if(tmp < f[v].w - e[i].w){
tmp = f[v].w - e[i].w;
p = v;
}
}
if(tmp == -1)
break;
ans -= tmp;
int u = f[p].a, v = f[p].b;
tree[mkp(u, v)] = tree[mkp(v, u)] = false;
tree[mkp(1, p)] = tree[mkp(p, 1)] = true;
}
cout<<"Total miles driven: "<<ans;
return 0;
}
沙漠之王
题意:
边有成本和长度两个参数。求图的生成树,使边的总成本与边的总长度的比值最小
解析:
0/1分数规划。
设边的成本为 a i a_i ai,长度为 b i b_i bi。
设最小值的估计值为 L L L,答案为 M I N MIN MIN,判断是否存在一棵生成树,满足 ∑ ( a i − L × b i ) < 0 \sum(a_i-L\times b_i) < 0 ∑(ai−L×bi)<0。
-
如果存在一棵生成树,使 ∑ ( a i − L × b i ) < 0 \sum(a_i-L\times b_i) < 0 ∑(ai−L×bi)<0 成立,变形得 ∑ a i ∑ b i < L \frac{\sum a_i}{\sum b_i} < L ∑bi∑ai<L。则 M I N < L MIN < L MIN<L
-
如果对于任意生成树,都有 ∑ ( a i − L × b i ) ≥ 0 \sum(a_i-L\times b_i) \ge 0 ∑(ai−L×bi)≥0,即 ∑ a i ∑ b i ≥ L \frac{\sum a_i}{\sum b_i} \ge L ∑bi∑ai≥L ,则 M I N ≥ L MIN \ge L MIN≥L
L L L 关于解的存在具有单调性。所以可以二分这个 L L L,然后判断最小生成树的权值和是否大于零
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define fi first
#define se second
#define debug(x) cerr << #x << ": " << (x) << endl
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
const int maxn = 1e3+10;
const int maxm = 1e5+10;
const int INF = 0x3f3f3f3f;
const double DINF = 1e18;
const double eps = 1e-8;
typedef pair<int, int> pii;
double a[maxn][maxn];
double b[maxn][maxn];
double dis[maxn];
int vis[maxn];
struct point{
double x, y, z;
}p[maxn];
double getdis(point i, point j){
return (double)sqrt((i.x-j.x)*(i.x-j.x) + (i.y-j.y)*(i.y-j.y));
}
int n;
int check(double x){
memset(vis, 0, sizeof(vis));
fill(dis, dis+1+n, DINF);
dis[1] = 0;
double res = 0;
for(int i = 1; i <= n; i++){
double tmp = DINF;
int po = 1;
for(int j = 1; j <= n; j++){
if(!vis[j] && tmp > dis[j]){
tmp = dis[j];
po = j;
}
}
vis[po] = 1;
res += dis[po];
for(int j = 1; j <= n; j++){
if(!vis[j] && dis[j] > fabs(p[po].z-p[j].z) - x*getdis(p[po], p[j]))
dis[j] = fabs(p[po].z-p[j].z) - x*getdis(p[po], p[j]);
}
}
return res >= 0.0;
}
void solve(){
for(int i = 1; i <= n; i++){
cin >> p[i].x >> p[i].y >> p[i].z;
}
double l = 0, r = 10.0;
double ans = 0;
while(fabs(r-l) > eps){
double mid = (l+r)/2;
if(check(mid)){
ans = mid;
l = mid;
}
else
r = mid;
}
cout << fixed << setprecision(3) << ans << endl;
return;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
while(1){
cin >> n;
if(n == 0)
break;
solve();
}
return 0;
}
黑暗城堡
题意:
求生成树的方案数,使该方案节点 1 1 1 与任意节点的距离 与 原图节点 1 1 1 与任意节点的最短距离相等
解析:
询问最短路径树的个数。文章来源:https://www.toymoban.com/news/detail-458251.html
Dijkstra 求出单源最短路。然后枚举每一个节点,有多少条边到这个点的路径长度为最短路径。文章来源地址https://www.toymoban.com/news/detail-458251.html
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define int ll
#define fi first
#define se second
#define debug(x) cerr << #x << ": " << (x) << endl
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
const int maxn = 1e5+10;
const int maxm = 1e6+10;
const int INF = 0x3f3f3f3f;
const ll mod = (1ll << 31) - 1;
typedef pair<int, int> pii;
int head[maxn], tot;
int dis[maxn], vis[maxn];
struct edge{
int to, nxt, w;
}e[maxm << 1];
int n, m;
int cnt[maxn];
void add(int a, int b, int c){
e[++tot].nxt = head[a];
e[tot].to = b;
e[tot].w = c;
head[a] = tot;
}
void Dijkstra(int st){
memset(dis, INF, sizeof(dis));
dis[st] = 0;
memset(vis, 0, sizeof(vis));
for(int i = 1; i < n; i++){
int p = -1, d = INF;
for(int j = 1; j <= n; j++){
if(vis[j])
continue;
if(dis[j] < d){
d = dis[j];
p = j;
}
}
vis[p] = 1;
for(int j = head[p]; j; j = e[j].nxt){
int v = e[j].to;
dis[v] = min(dis[v], dis[p] + e[j].w);
}
}
}
/*
ll prim(int st){
memset(vis, 0, sizeof(vis));
vis[st] = 1;
ll res = 1;
for(int i = 1; i < n; i++){
int p = -1;
int d = INF;
for(int j = 2; j <= n; j++){
if(vis[j])
continue;
if(dis[j] < d){
d = dis[j];
p = j;
}
}
vis[p] = 1;
ll cnt = 0;
for(int j = head[p]; j; j = e[j].nxt){
int v = e[j].to;
if(dis[p] == dis[v] + e[j].w)
cnt++;
}
res = res * cnt % mod;
}
return res;
}*/
signed main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> m;
for(int i = 1, u, v, w; i <= m; i++){
cin >> u >> v >> w;
add(u, v, w);
add(v, u, w);
}
Dijkstra(1);
for(int u = 1; u <= n; u++){
for(int i = head[u]; i; i = e[i].nxt){
int v = e[i].to;
if(dis[v] == dis[u] + e[i].w)
cnt[v]++;
}
}
ll res = 1;
for(int i = 2; i <= n; i++)
res = (res * cnt[i]) % mod;
cout << res << endl;
return 0;
}
到了这里,关于《算法竞赛进阶指南》0x62 最小生成树的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!