【图论2-3】最小生成树

试想在一个平原国家中有若干城市,要建立若干高速公路,这些高速公路可以建成直线,连 接在两个城市之间。如何规划这些高速公路使建造距离总和最短,就是一种典型的最小生成树问 题。

树是一种特殊的图,具有很多特殊的性质。生成树问题研究的是将图中的所有顶点保留,但 只选择图中的部分边,得到一棵树(也就是图的生成树)的问题。最小生成树则是在这些生成树 中,边权之和最小的生成树。可以使用 Prim 算法或者 Kruska 算法求解最小生成树。

对应进阶篇第 11 章。

img

【模板】最小生成树

题目描述

如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出 orz

输入格式

第一行包含两个整数 \(N,M\),表示该图共有 \(N\) 个结点和 \(M\) 条无向边。

接下来 \(M\) 行每行包含三个整数 \(X_i,Y_i,Z_i\),表示有一条长度为 \(Z_i\) 的无向边连接结点 \(X_i,Y_i\)

输出格式

如果该图连通,则输出一个整数表示最小生成树的各边的长度之和。如果该图不连通则输出 orz

样例 #1

样例输入 #1

1
2
3
4
5
6
4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3

样例输出 #1

1
7

提示

数据规模:

对于 \(20\%\) 的数据,\(N\le 5\)\(M\le 20\)

对于 \(40\%\) 的数据,\(N\le 50\)\(M\le 2500\)

对于 \(70\%\) 的数据,\(N\le 500\)\(M\le 10^4\)

对于 \(100\%\) 的数据:\(1\le N\le 5000\)\(1\le M\le 2\times 10^5\)\(1\le Z_i \le 10^4\)

样例解释:

所以最小生成树的总边权为 \(2+2+3=7\)

Kruska算法

先把边按照权值进行排序,用贪心的思想优先选取权值较小的边,并依次连接,若出现环则跳过此边(用并查集来判断是否存在环)继续搜,直到已经使用的边的数量比总点数少一即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include<bits/stdc++.h>
using namespace std;
int n,m,i,j,u,v,total;
struct edge{
int start;
int to;
long long val;
}bian[100000000];
int f[10000000];
long long ans;
int find(int x)
{
if(f[x]==x)
{
return x;
}
else
{
return f[x]=find(f[x]);
}
}
bool cmp(edge a,edge b)
{
return a.val<b.val;
}
void kruskal()
{
for(int i=1;i<=m;i++)
{
u=find(bian[i].start);
v=find(bian[i].to);
if(u==v)
{
continue;
}
ans+=bian[i].val;
f[u]=v;
total++;
if(total==n-1)
{
break;
}
//循环结束条件
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++){
f[i]=i;
}
//初始化并查集
for(int i=1;i<=m;i++)
{
cin>>bian[i].start>>bian[i].to>>bian[i].val;
}
//从哪里开始,到哪里结束,和权值
//因为实际上是要存边
sort(bian+1,bian+m+1,cmp);
kruskal();
if(total==n-1)
{
cout<<ans;
}
else
{
cout<<"orz";
}
}

prim算法

prim算法基于贪心,我们每次总是选出一个离生成树距离最小的点去加入生成树,最后实现最小生成树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include<bits/stdc++.h>
using namespace std;

int k,n,m,cnt,sum,ai,bi,ci,head[5005],dis[5005],vis[5005];

struct Edge
{
int v,w,next;
}e[400005];
void add(int u,int v,int w)
{
e[++k].v=v;
e[k].w=w;
e[k].next=head[u];
head[u]=k;
}
//经典链式前向星,不会的话,我这篇写完下一篇就介绍这个()
typedef pair <int,int> pii;
priority_queue <pii,vector<pii>,greater<pii> > q;
//最小堆,先取出的是最小的意思
void prim()
{
dis[1]=0;
q.push(make_pair(0,1));
while(!q.empty()&&cnt<n)
{
int d=q.top().first,u=q.top().second;
q.pop();
if(vis[u]) continue;
cnt++;
sum+=d;
vis[u]=1;
for(int i=head[u];i!=-1;i=e[i].next)
if(e[i].w<dis[e[i].v])
{
dis[e[i].v]=e[i].w;
q.push(make_pair(dis[e[i].v],e[i].v));
}
}
}

int main()
{
memset(dis,127,sizeof(dis));
//初始化为无限大
memset(head,-1,sizeof(head));
//链式前向星操作了
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>ai>>bi>>ci;
add(ai,bi,ci);
add(bi,ai,ci);
//存双向边
}
prim();
if (cnt==n)cout<<sum;
//如果是
else cout<<"orz";
}

买礼物

题目描述

又到了一年一度的明明生日了,明明想要买 \(B\) 样东西,巧的是,这 \(B\) 样东西价格都是 \(A\) 元。

但是,商店老板说最近有促销活动,也就是:

如果你买了第 \(I\) 样东西,再买第 \(J\) 样,那么就可以只花 \(K_{I,J}\) 元,更巧的是,\(K_{I,J}\) 竟然等于 \(K_{J,I}\)

现在明明想知道,他最少要花多少钱。

输入格式

第一行两个整数,\(A,B\)

接下来 \(B\) 行,每行 \(B\) 个数,第 \(I\) 行第 \(J\) 个为 \(K_{I,J}\)

我们保证 \(K_{I,J}=K_{J,I}\) 并且 \(K_{I,I}=0\)

特别的,如果 \(K_{I,J}=0\),那么表示这两样东西之间不会导致优惠。

注意 \(K_{I,J}\) 可能大于 \(A\)

输出格式

一个整数,为最小要花的钱数。

样例 #1

样例输入 #1

1
2
1 1
0

样例输出 #1

1
1

样例 #2

样例输入 #2

1
2
3
4
3 3
0 2 4
2 0 2
4 2 0

样例输出 #2

1
7

提示

样例解释 \(2\)

先买第 \(2\) 样东西,花费 \(3\) 元,接下来因为优惠,买 \(1,3\) 样都只要 \(2\) 元,共 \(7\) 元。

(同时满足多个“优惠”的时候,聪明的明明当然不会选择用 \(4\) 元买剩下那件,而选择用 \(2\) 元。)

数据规模

对于 \(30\%\) 的数据,\(1\le B\le 10\)

对于 \(100\%\) 的数据,\(1\le B\le500,0\le A,K_{I,J}\le1000\)

2018.7.25新添数据一组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include<bits/stdc++.h>
using namespace std;
int n,m,cnt,flag,px,py,ans,f[10000];
struct p
{
int x,y,z;
}a[250008];
int find(int x){
if (x==f[x]) return x;
return f[x]=find(f[x]);
}
bool cmp(p a,p b){
return a.z<b.z;
}
int main(){
cin>>n>>m;
for (int i=0;i<=m;i++) f[i]=i;
//并查集初始化
for (int i=1;i<=m;i++)
{
cnt++;
a[cnt].x=0;
a[cnt].y=i;
a[cnt].z=n;
}
//每一个都可以单独购买就是说
for (int i=1;i<=m;i++)
for (int j=1;j<=m;j++)
{
cin>>falg;
if (flag){
cnt++;
a[cnt].x=i;
a[cnt].y=j;
a[cnt].z=flag;
}
}
//这个就是优惠的,买了i就可以把j也并进去。
sort(a+1,a+cnt+1,cmp);
//排序,保证算法正确性
for (int i=1;i<=cnt;i++)
{
px=find(a[i].x);
//找x的祖先
py=find(a[i].y);
//找y的祖先
if (px==py) continue;
f[px]=py;
//赋值祖先
ans+=a[i].z;
//统计答案
}
cout<<ans;
//输出答案
}

[BJWC2010] 严格次小生成树

题目描述

小 C 最近学了很多最小生成树的算法,Prim 算法、Kruskal 算法、消圈算法等等。正当小 C 洋洋得意之时,小 P 又来泼小 C 冷水了。小 P 说,让小 C 求出一个无向图的次小生成树,而且这个次小生成树还得是严格次小的,也就是说:如果最小生成树选择的边集是 \(E_M\),严格次小生成树选择的边集是 \(E_S\),那么需要满足:(\(value(e)\) 表示边 \(e\) 的权值) \(\sum_{e \in E_M}value(e)<\sum_{e \in E_S}value(e)\)

这下小 C 蒙了,他找到了你,希望你帮他解决这个问题。

输入格式

第一行包含两个整数 \(N\)\(M\),表示无向图的点数与边数。

接下来 \(M\) 行,每行 \(3\) 个数 \(x,y,z\) 表示,点 \(x\) 和点 \(y\) 之间有一条边,边的权值为 \(z\)

输出格式

包含一行,仅一个数,表示严格次小生成树的边权和。

样例 #1

样例输入 #1

1
2
3
4
5
6
7
5 6
1 2 1
1 3 2
2 4 3
3 5 4
3 4 3
4 5 6

样例输出 #1

1
11

提示

数据中无向图不保证无自环

对于 \(50\%\) 的数据, \(N\le 2000\)\(M\le 3000\)

对于 \(80\%\) 的数据, \(N\le 5\times 10^4\)\(M\le 10^5\)

对于 \(100\%\) 的数据, \(N\le 10^5\)\(M\le 3\times10^5\),边权 \(\in [0,10^9]\),数据保证必定存在严格次小生成树。

选择1条边做替换最优(也就是最小生成树和次小生成树中权值不相同的边有且只有1条)

我们要用的算法和数据结构吧:

1. 求最小生成树:并查集,Kruskal,无脑存边

2. 最近公共祖先LCA:树上倍增,dfs预处理,邻接表

3. 区间最大/次大RMQ:树上st表

4. 一句提醒:不开long long见祖宗

我宣布这份代码非常不错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
const int maxn = 100010;
const ll inf = 1e18;
// 赋个极大值
int n, m;
ll MST, ans = inf;
// ans是次小生成树,MST是最小生成树
struct Node
{
int to;
// 到达点
ll cost;
// 边权
};
vector<Node> v[maxn];
// 邻接表存最小生成树
int dep[maxn];
// dep[i]表示第i个点的深度
bool vis[maxn];
ll f[maxn][25];
// f[i][j]存节点i的2^j级祖先是谁
ll g1[maxn][25];
// g1[i][j]存节点i到他的2^j级祖先路径上最大值
ll g2[maxn][25];
// g2[i][j]存节点i到他的2^j级祖先路径上次大值
void dfs(const int x)
{
// 因为是树上st和树上倍增,所以可以一起预处理
vis[x] = true;
// x号点访问过了
for (int i = 0; i < v[x].size(); i++)
{
// 扫所有出边
int y = v[x][i].to;
if (vis[y])
continue;
// 儿子不能被访问过
dep[y] = dep[x] + 1;
// 儿子的深度是父亲+1
f[y][0] = x;
// 儿子y的2^0级祖先是父亲x
g1[y][0] = v[x][i].cost;
// y到他的2^0级祖先的最大边长
g2[y][0] = -inf;
// y到他的2^0级祖先的次大边长(没有次大边长,故为-inf)
dfs(y);
// 递归预处理
}
}
inline void prework()
{
// 暴力预处理
for (int i = 1; i <= 20; i++)
// 枚举2^1-2^20
for (int j = 1; j <= n; j++)
{
// 枚举每个点
f[j][i] = f[f[j][i - 1]][i - 1];
// 正常的倍增更新
g1[j][i] = max(g1[j][i - 1], g1[f[j][i - 1]][i - 1]);
g2[j][i] = max(g2[j][i - 1], g2[f[j][i - 1]][i - 1]);
// 以下是求次大的精华了
if (g1[j][i - 1] > g1[f[j][i - 1]][i - 1])
g2[j][i] = max(g2[j][i], g1[f[j][i - 1]][i - 1]);
// j的2^i次大值,是j的2^(i-1)和j^2(i-1)的2^(i-1)最大值中的较小的那一个
// 特别的,如果这两个相等,那么没有次大值,不更新g2数组
else if (g1[j][i - 1] < g1[f[j][i - 1]][i - 1])
g2[j][i] = max(g2[j][i], g1[j][i - 1]);
}
}
inline void LCA(int x, int y, const ll w)
{
// 非树边连接x,y权值为w
// 求LCA时候直接更新答案
ll zui = -inf, ci = -inf;
// zui表示最大值,ci表示次大值
if (dep[x] > dep[y])
swap(x, y);
// 保证y比x深
for (int i = 20; i >= 0; i--)
// 倍增向上处理y
if (dep[f[y][i]] >= dep[x])
{
zui = max(zui, g1[y][i]);
// 更新路径最大值
ci = max(ci, g2[y][i]);
// 更新路径次大值
y = f[y][i];
}
if (x == y)
{
if (zui != w)
ans = min(ans, MST - zui + w);
// 如果最大值和w不等,用最大值更新
else if (ci != w && ci > 0)
ans = min(ans, MST - ci + w);
// 有毒瘤情况,没有次大值,此时也不能用次大值更新
return;
}
for (int i = 20; i >= 0; i--)
if (f[x][i] != f[y][i])
{
zui = max(zui, max(g1[x][i], g1[y][i]));
ci = max(ci, max(g2[x][i], g2[y][i]));
x = f[x][i];
y = f[y][i];
}
// 依旧是普通的更新最大、次大值
zui = max(zui, max(g1[x][0], g1[y][0]));
// 更新最后一步的最大值
// 注意下面这两句又凝结了人类智慧精华
// 因为次大值有可能出现在最后一步上,所以在更新答案前还要更新一下ci
// 如果最后两边的某一边是最大值,ci就只能对另一边取max
if (g1[x][0] != zui)
ci = max(ci, g1[x][0]);
if (g2[y][0] != zui)
ci = max(ci, g2[y][0]);
if (zui != w)
ans = min(ans, MST - zui + w);
else if (ci != w && ci > 0)
ans = min(ans, MST - ci + w);
// 依旧特判毒瘤情况
}

struct Edge
{
int from, to; // 连接from和to两个点
ll cost; // 边权
bool is_tree; // 记录是不是树边
} edge[maxn * 3];
bool operator<(const Edge x, const Edge y)
{ return x.cost < y.cost; }
// 重载运算符,按照边权从大到小排序
int fa[maxn]; // 并查集数组
inline int find(const int x)
{
if (fa[x] == x)
return x;
else
return fa[x] = find(fa[x]);
} // 查询包含x的集合的代表元素
inline void Kruskal()
{
sort(edge, edge + m);
// 先排序,保证贪心结果的正确性
for (int i = 1; i <= n; i++)
// 初始化并查集
fa[i] = i;
for (int i = 0; i < m; i++)
{
int x = edge[i].from;
int y = edge[i].to;
ll z = edge[i].cost;
int a = find(x), b = find(y);
if (a == b)
continue;
// 如果x和y已经连通,continue掉
fa[find(x)] = y;
// 合并x,y所在集合
MST += z;
// 求最小生成树
edge[i].is_tree = true;
// 标记为树边
v[x].push_back((Node){y, z});
// 邻接表记录下树边
v[y].push_back((Node){x, z});
}
}

int main()
{
cin>>n>>m;
for (int i = 0, x, y; i < m; i++)
{
ll z;
cin>>x>>y;
cin>>z;
if (x == y)
continue;
edge[i].from = x;
edge[i].to = y;
edge[i].cost = z;
} // 读入整个图
Kruskal(); // 初始化最小生成树
dep[1] = 1; // 设1号点是根节点,把它变成有根树
dfs(1); // 从1开始预处理
prework(); // 倍增预处理
for (int i = 0; i < m; i++) // 枚举所有边
if (!edge[i].is_tree) // 如果是非树边,那么更新答案
LCA(edge[i].from, edge[i].to, edge[i].cost);
cout<<ans; // 输出答案,不开long long见祖宗
return 0; // 我谔谔终于结束
}

营救

题目背景

“咚咚咚……”“查水表!”原来是查水表来了,现在哪里找这么热心上门的查表员啊!小明感动得热泪盈眶,开起了门……

题目描述

妈妈下班回家,街坊邻居说小明被一群陌生人强行押上了警车!妈妈丰富的经验告诉她小明被带到了 \(t\) 区,而自己在 \(s\) 区。

该市有 \(m\) 条大道连接 \(n\) 个区,一条大道将两个区相连接,每个大道有一个拥挤度。小明的妈妈虽然很着急,但是不愿意拥挤的人潮冲乱了她优雅的步伐。所以请你帮她规划一条从 \(s\)\(t\) 的路线,使得经过道路的拥挤度最大值最小。

输入格式

第一行有四个用空格隔开的 \(n\)\(m\)\(s\)\(t\),其含义见【题目描述】。

接下来 \(m\) 行,每行三个整数 \(u, v, w\),表示有一条大道连接区 \(u\) 和区 \(v\),且拥挤度为 \(w\)

两个区之间可能存在多条大道

输出格式

输出一行一个整数,代表最大的拥挤度。

样例 #1

样例输入 #1

1
2
3
4
3 3 1 3
1 2 2
2 3 1
1 3 3

样例输出 #1

1
2

提示

数据规模与约定

  • 对于 \(30\%\) 的数据,保证 \(n\leq 10\)
  • 对于 \(60\%\) 的数据,保证 \(n\leq 100\)
  • 对于 \(100\%\) 的数据,保证 \(1 \leq n\leq 10^4\)\(1 \leq m \leq 2 \times 10^4\)\(w \leq 10^4\)\(1 \leq s, t \leq n\)。且从 \(s\) 出发一定能到达 \(t\) 区。

样例输入输出 1 解释

小明的妈妈要从 \(1\) 号点去 \(3\) 号点,最优路线为 \(1\)->\(2\)->\(3\)

将边从小到大排序,然后克鲁斯卡尔最小生成树连边,这样当S和T第一次联通时,当前边的权值就是答案了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include<bits/stdc++.h> // 万能头文件堆没用的头文件 
#define LL unsigned long long
using namespace std;
int n,m,s,t,i;
struct hhh
{
int u,v,c;
}a[100000];
int f[20000];
int cmp(const hhh &a,const hhh &b)
{
return a.c<b.c;
}
int gf(int x)
{
if (f[x]==x)
return x;
else
//如果爸爸是本身就返回自己
{
return f[x]=gf(f[x]);
//返回祖宗
}
}
void un(int x,int y)//合并
{
int xx=gf(x);
//找x的祖宗
int yy=gf(y);
//找y的祖宗
if (xx!=yy)
//如果祖宗不同
{
f[xx]=yy;
//合并祖宗
}
return;
}
int main()
{
cin>>n>>m>>s>>t;
//n为城市数,m为边数,s为起点,t为终点
for (i=1;i<=m;i++)
cin>>a[i].u>>a[i].v>>a[i].c;
sort(a+1,a+m+1,cmp);//边权值从小到大排序
int haha=0;int sum=0;
for (i=1;i<=n;i++)//f数组初始化
{
f[i]=i;
}
for (i=1;i<=m&&haha<=n-1;i++)
{
if (gf(a[i].u)!=gf(a[i].v))//判断是否在集合内
{
un(a[i].u,a[i].v);//合并
haha++;
}
if (gf(s)==gf(t))//起点和终点在同一个集合。
{
cout<<a[i].c;
return 0;
}
}
return 0;
}

口袋的天空

题目背景

小杉坐在教室里,透过口袋一样的窗户看口袋一样的天空。

有很多云飘在那里,看起来很漂亮,小杉想摘下那样美的几朵云,做成棉花糖。

题目描述

给你云朵的个数 \(N\),再给你 \(M\) 个关系,表示哪些云朵可以连在一起。

现在小杉要把所有云朵连成 \(K\) 个棉花糖,一个棉花糖最少要用掉一朵云,小杉想知道他怎么连,花费的代价最小。

输入格式

第一行有三个数 \(N,M,K\)

接下来 \(M\) 行每行三个数 \(X,Y,L\),表示 \(X\) 云和 \(Y\) 云可以通过 \(L\) 的代价连在一起。

输出格式

对每组数据输出一行,仅有一个整数,表示最小的代价。

如果怎么连都连不出 \(K\) 个棉花糖,请输出 No Answer

样例 #1

样例输入 #1

1
2
3 1 2
1 2 1

样例输出 #1

1
1

提示

对于 \(30\%\) 的数据,\(1 \le N \le 100\)\(1\le M \le 10^3\)

对于 \(100\%\) 的数据,\(1 \le N \le 10^3\)\(1 \le M \le 10^4\)\(1 \le K \le 10\)\(1 \le X,Y \le N\)\(0 \le L<10^4\)

连的边数 得到的树的个数

n-1 1

n-2 2

n-3 3

... ...

n-k k

所以我们如果想要连出k棵树,就需要连n-k条边。

题目要求用n朵云连出k个棉花糖。

因为每个棉花糖都是连通的,

那么每个棉花糖就相当于是一棵树。

就是说要用n个节点连出k棵树。

也就是说要用n-k条边连出k棵树。

也就是说要花费连出n-k条边的代价。

既然一定要花费连出n-k条边的代价,

那么当然要选择代价最小的边连起来。

所以给每条可以连的边按代价从小到大排个序,

然后连n-k条边造k个最小生成树就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include<bits/stdc++.h>
using namespace std;
struct bian{
int s,e,w;
}a[10000000];
int f[10000000];
bool cmp(bian x,bian y)
{
return x.w<y.w;
}
int find(int x)
{
if(f[x]==x)
{
return x;
}else{
return f[x]=find(f[x]);
}
}
int n,m,k;
int main()
{cin>>n>>m>>k;
for(int i=1;i<=n;i++)
{
f[i]=i;
}
for(int i=1;i<=m;i++)
{
cin>>a[i].s>>a[i].e>>a[i].w;
}
sort(a+1,a+1+m,cmp);
int cnt=0;
int sum=0;
for(int i=1;i<=m;i++)
{
if(find(a[i].s)!=find(a[i].e ))
{
f[find(a[i].s)]=f[find(a[i].e)];
sum+=a[i].w;
cnt++;
}
if(cnt>=n-k)
{
break;
}
}
if(cnt>=n-k)
{
cout<<sum;
}else{
cout<<"No Answer";
}
}

[USACO08OCT] Watering Hole G

题目描述

Farmer John 的农场缺水了。

他决定将水引入到他的 \(n\) 个农场。他准备通过挖若干井,并在各块田中修筑水道来连通各块田地以供水。在第 \(i\) 号田中挖一口井需要花费 \(W_i\) 元。连接 \(i\) 号田与 \(j\) 号田需要 \(P_{i,j}\)\(P_{j,i}=P_{i,j}\))元。

请求出 FJ 需要为使所有农场都与有水的农场相连或拥有水井所需要的最少钱数。

输入格式

第一行为一个整数 \(n\)

接下来 \(n\) 行,每行一个整数 \(W_i\)

接下来 \(n\) 行,每行 \(n\) 个整数,第 \(i\) 行的第 \(j\) 个数表示连接 \(i\) 号田和 \(j\) 号田需要的费用 \(P_{i,j}\)

输出格式

输出最小开销。

样例 #1

样例输入 #1

1
2
3
4
5
6
7
8
9
4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0

样例输出 #1

1
9

提示

对于 \(100\%\) 的数据,\(1 \leq n \leq 300\)\(1 \leq W_i \leq 10^5\)\(0 \leq P_{i,j} \leq 10^5\)

图中有几个点?有n+1个。

构图时,我们只需多加一个点(n+1),就是说可以单独挖井的意思,对于每个点i,我们连边i→n+1,边权为w_i。然后直接跑最小生成树就没了。就没了。

因此还是比较简单的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include<bits/stdc++.h>
using namespace std;
struct bian{
int from;
int to;
int val;
}a[1000000];
int n,w[10000];
int f[100000];
int p[1001][1001];
int te;
int ans;
bool cmp(bian x ,bian y)
{
return x.val<y.val;
}
int find(int x)
{
if(x==f[x])
{
return x;
}else{
return f[x]=find(f[x]);
}
}
void adde(int x,int y,int z)
{
++te;
a[te].to=y;
a[te].from=x;
a[te].val=z;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>w[i];
f[i]=i;
adde(i,n+1,w[i]);
adde(n+1,i,w[i]);
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
cin>>p[i][j];
if(i!=j)
{
adde(i,j,p[i][j]);
}
}
}
sort(a+1,a+1+te,cmp);
for(int i=1;i<=te;i++)
{
int x=find(a[i].to);
int y=find(a[i].from);
if(x==y)
{
continue;
}
else{
f[x]=y;
ans+=a[i].val;
}
}
cout<<ans;
}

[NOIP2013 提高组] 货车运输

题目背景

NOIP2013 提高组 D1T3

题目描述

A 国有 \(n\) 座城市,编号从 \(1\)\(n\),城市之间有 \(m\) 条双向道路。每一条道路对车辆都有重量限制,简称限重。

现在有 \(q\) 辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。

输入格式

第一行有两个用一个空格隔开的整数 $ n,m$,表示 A 国有 $ n$ 座城市和 \(m\) 条道路。

接下来 \(m\) 行每行三个整数 \(x, y, z\),每两个整数之间用一个空格隔开,表示从 $x $ 号城市到 $ y $ 号城市有一条限重为 \(z\) 的道路。
注意: \(x \neq y\),两座城市之间可能有多条道路 。

接下来一行有一个整数 \(q\),表示有 \(q\) 辆货车需要运货。

接下来 \(q\) 行,每行两个整数 \(x,y\),之间用一个空格隔开,表示一辆货车需要从 \(x\) 城市运输货物到 \(y\) 城市,保证 \(x \neq y\)

输出格式

共有 \(q\) 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。
如果货车不能到达目的地,输出 \(-1\)

样例 #1

样例输入 #1

1
2
3
4
5
6
7
8
4 3
1 2 4
2 3 3
3 1 1
3
1 3
1 4
1 3

样例输出 #1

1
2
3
3
-1
3

提示

对于 \(30\%\) 的数据,\(1 \le n < 1000\)\(1 \le m < 10,000\)\(1\le q< 1000\)

对于 \(60\%\) 的数据,\(1 \le n < 1000\)\(1 \le m < 5\times 10^4\)\(1 \le q< 1000\)

对于 \(100\%\) 的数据,\(1 \le n < 10^4\)\(1 \le m < 5\times 10^4\),$1 q< 3^4 $,\(0 \le z \le 10^5\)

本题思路:

在最大生成树上找一条最小边,实现的过程就是在最大生成树上跑倍增LCA。

我们就要单独把最大生成树建好(除去多余边),所以我们就需要在Kruskal的时候直接建树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void kruskal()
{
sort(edge + 1, edge + 1 + m, cmp);
for (int i = 1; i <= n; i ++ )
{
fa[i] = i;
}
for (int i = 1; i <= m; i ++ )
{
int fx = find(edge[i].x);
int fy = find(edge[i].y);
if (fx != fy)
{
fa[fx] = fy;
add(edge[i].x, edge[i].y, edge[i].val);
add(edge[i].y, edge[i].x, edge[i].val);
}
}
}

DFS做几个处理:

1.处理出每个点的深度(LCA)

2.处理好每个点的上1个点(LCA)

3.处理好每个点到它的上一个点的距离(LCA边权)

1
2
3
4
5
6
7
8
9
10
11
12
13
void dfs(int x)
{
for (int i = head[x]; i; i = nex[i])
{
int y = to[i];
if (deep[y])
continue ;
deep[y] = deep[x] + 1;
f[y][0] = x;
w[y][0] = val[i];
dfs(y);
}
}
1
ans = min(ans, min(w[x][i], w[y][i]));

最终的ans就是对于这一对询问的答案。

LCA部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int lca(int x, int y)
{
if (find(x) != find(y))
return -1;
int ans = inf;
if (deep[x] > deep[y])
swap(x, y);
for (int i = 20; i >= 0; i -- )
{
if (deep[f[y][i]] >= deep[x])
{
ans = min(ans, w[y][i]);
y = f[y][i];
}
}
if (x == y) return ans;
for (int i = 20; i >= 0; i -- )
{
if (f[x][i] != f[y][i])
{
ans = min(ans, min(w[x][i], w[y][i]));
y = f[y][i];
x = f[x][i];
}
}
ans = min(ans, min(w[x][0], w[y][0]));
return ans;
}

最终代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#include <bits/stdc++.h>

using namespace std;

const int inf = 0x3f3f3f3f;

const int MAXN = 10010;
const int MAXM = 50050;

int n, m, q;

int fa[MAXN], deep[MAXN], f[MAXN][21], w[MAXN][21];

int tot, to[MAXM], val[MAXM], nex[MAXM], head[MAXN];

struct Edge
{
int x, y, val;
} edge[MAXM];
//存边

bool cmp(Edge a, Edge b)
{
return a.val > b.val;
}
//排序,最大生成树

int find(int x)
{
if (fa[x] == x)
return x;
return fa[x] = find(fa[x]);
}
//并查集查找

void add(int x, int y, int z)
{
to[++tot] = y;
val[tot] = z;
nex[tot] = head[x];
head[x] = tot;
}
//链式前向星添树边

void kruskal()
{
sort(edge + 1, edge + 1 + m, cmp);
for (int i = 1; i <= n; i++)
{
fa[i] = i;
}
for (int i = 1; i <= m; i++)
{
int fx = find(edge[i].x);
int fy = find(edge[i].y);
if (fx != fy)
{
fa[fx] = fy;
add(edge[i].x, edge[i].y, edge[i].val);
add(edge[i].y, edge[i].x, edge[i].val);
}
}
}
//跑最大生成树,不解释

void dfs(int x)
{
for (int i = head[x]; i; i = nex[i])
{
int y = to[i];
if (deep[y])
continue;
deep[y] = deep[x] + 1;
//深度+1
f[y][0] = x;
//父节点
w[y][0] = val[i];
//距离
dfs(y);
//开搜
}
}

int lca(int x, int y)
{
if (find(x) != find(y))
return -1;
//不在那一条线上
int ans = inf;
if (deep[x] > deep[y])
swap(x, y);
//保证y深
for (int i = 20; i >= 0; i--)
{
if (deep[f[y][i]] >= deep[x])
{
ans = min(ans, w[y][i]);
y = f[y][i];
}
}
//跳Y
if (x == y)
return ans;
//直接返回
for (int i = 20; i >= 0; i--)
{
if (f[x][i] != f[y][i])
{
ans = min(ans, min(w[x][i], w[y][i]));
y = f[y][i];
x = f[x][i];
}
}
//跳x,y
ans = min(ans, min(w[x][0], w[y][0]));
//和根节点比一下
return ans;
}

int main()
{
cin>>n>>m;
for (int i = 1; i <= m; i++)
{
cin>>edge[i].x>>edge[i].y>>edge[i].val;
}
kruskal();
cin>>q;
for (int i = 1; i <= n; i++)
{
if (!deep[i])
{
deep[i] = 1;
dfs(i);
f[i][0] = i;
w[i][0] = inf;
//节点很高,父节点是自己
}
}
for (int i = 1; i <= 20; i++)
{
for (int j = 1; j <= n; j++)
{
f[j][i] = f[f[j][i - 1]][i - 1];
w[j][i] = min(w[j][i - 1], w[f[j][i - 1]][i - 1]);
}
}
//预处理一下倍增
for (int i = 1; i <= q; i++)
{
int x, y;
cin>>x>>y;
cout<<lca(x, y)<<'\n';
//开始lca跳了
}
return 0;
}

逐个击破

题目背景

三大战役的平津战场上,傅作义集团在以北平、天津为中心,东起唐山西至张家口的铁路线上摆起子一字长蛇阵,并企图在溃败时从海上南逃或向西逃窜。为了就地歼敌不让其逃走,指挥官制定了先切断敌人东西两头退路然后再逐个歼灭敌人的战略方针。秉承伟大军事家的战略思想,作为一个有智慧的军长你,遇到了一个类似的战场局面。

题目描述

现在有 \(N\) 个城市,其中 \(K\) 个被敌方军团占领了,\(N\) 个城市间有 \(N-1\) 条公路相连,破坏其中某条公路的代价是已知的,现在,告诉你 \(K\) 个敌方军团所在的城市,以及所有公路破坏的代价,请你算出花费最少的代价将这 \(K\) 个地方军团互相隔离开,以便第二步逐个击破敌人。

输入格式

第一行包含两个正整数 \(N\)\(K\)

第二行包含 \(K\) 个整数,表示哪个城市被敌军占领。

接下来 \(N-1\) 行,每行包含三个正整数 \(a,b,c\),表示从 \(a\) 城市到 \(b\) 城市有一条公路,以及破坏的代价 \(c\)。城市的编号从 \(0\) 开始。

输出格式

输出一行一个整数,表示最少花费的代价。

样例 #1

样例输入 #1

1
2
3
4
5
6
5 3
1 2 4
1 0 4
1 3 8
2 1 1
2 4 3

样例输出 #1

1
4

提示

对于 \(10\%\) 的数据,\(N\le 10\)

对于 \(100\%\) 的数据,\(2\le N\le10^5\)\(2\le K\le N\)\(1\le c\le 10^6\)

最大生成树

花费最小代价删边,等价于花最大代价建边,最后剩下不建的边,就是我们的答案.

所以说,我们需要按照边权从大到小建图sort!

我们需要保证的是两个敌人节点不互相连通.

并查集!

如果,我方节点已经连接了敌方节点,则需要标记我方节点,使得敌方节点无法通过我方节点连接敌方节点.

因此说,我们可以把连接到敌人节点的我方节点变成敌人节点.

这就是w数组的含义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=2e5;
int f[N];
int n,k,tot;
int w[N];
int find(int x)
{
if (x == f[x])
return x;
return f[x] = find(f[x]);
}
struct node
{
int u,v,w;
}a[N];
bool cmp(node x,node y)
{
return x.w>y.w;
}
void add(int u,int v,int w)
{
a[++tot].u=u;
a[tot].v=v;
a[tot].w=w;
}
ll ans;
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++)
{
f[i]=i;
}
for(int i=1;i<=k;i++)
{
int x;
cin>>x;
w[x]=1;
}
for(int i=1;i<=n-1;i++)
{
int u,v,w;
cin>>u>>v>>w;
ans+=w;
add(u,v,w);
}
sort(a+1,a+n,cmp);
for(int i=1;i<=n-1;i++)
{
int px=find(a[i].u);
int py=find(a[i].v);
if(w[px]&&w[py])
{
continue;
}
else if(w[px]||w[py])
{
w[px]=1;
w[py]=1;
f[px]=py;
ans-=a[i].w;
}
else
{
f[px]=py;
ans-=a[i].w;
}
}
cout<<ans;
}

Shichikuji and Power Grid

题面翻译

已知一个平面上有 \(n\) 个城市,需要个 \(n\) 个城市均通上电。
一个城市有电,必须在这个城市有发电站或者和一个有电的城市用电缆相连。

在一个城市建造发电站的代价是 \(c[i]\),将 \(i\)\(j\) 两个城市用电缆相连的代价是 \(k[i]+k[j]\) 乘上两者的曼哈顿距离。

求最小代价的方案。

题目描述

Shichikuji is the new resident deity of the South Black Snail Temple. Her first job is as follows:

There are $ n $ new cities located in Prefecture X. Cities are numbered from $ 1 $ to $ n $ . City $ i $ is located $ x_i $ km North of the shrine and $ y_i $ km East of the shrine. It is possible that $ (x_i, y_i) = (x_j, y_j) $ even when $ i j $ .

Shichikuji must provide electricity to each city either by building a power station in that city, or by making a connection between that city and another one that already has electricity. So the City has electricity if it has a power station in it or it is connected to a City which has electricity by a direct connection or via a chain of connections.

  • Building a power station in City $ i $ will cost $ c_i $ yen;
  • Making a connection between City $ i $ and City $ j $ will cost $ k_i + k_j $ yen per km of wire used for the connection. However, wires can only go the cardinal directions (North, South, East, West). Wires can cross each other. Each wire must have both of its endpoints in some cities. If City $ i $ and City $ j $ are connected by a wire, the wire will go through any shortest path from City $ i $ to City $ j $ . Thus, the length of the wire if City $ i $ and City $ j $ are connected is $ |x_i - x_j| + |y_i - y_j| $ km.

Shichikuji wants to do this job spending as little money as possible, since according to her, there isn't really anything else in the world other than money. However, she died when she was only in fifth grade so she is not smart enough for this. And thus, the new resident deity asks for your help.

And so, you have to provide Shichikuji with the following information: minimum amount of yen needed to provide electricity to all cities, the cities in which power stations will be built, and the connections to be made.

If there are multiple ways to choose the cities and the connections to obtain the construction of minimum price, then print any of them.

输入格式

First line of input contains a single integer $ n $ ( $ 1 n $ ) — the number of cities.

Then, $ n $ lines follow. The $ i $ -th line contains two space-separated integers $ x_i $ ( $ 1 x_i ^6 $ ) and $ y_i $ ( $ 1 y_i ^6 $ ) — the coordinates of the $ i $ -th city.

The next line contains $ n $ space-separated integers $ c_1, c_2, , c_n $ ( $ 1 c_i ^9 $ ) — the cost of building a power station in the $ i $ -th city.

The last line contains $ n $ space-separated integers $ k_1, k_2, , k_n $ ( $ 1 k_i ^9 $ ).

输出格式

In the first line print a single integer, denoting the minimum amount of yen needed.

Then, print an integer $ v $ — the number of power stations to be built.

Next, print $ v $ space-separated integers, denoting the indices of cities in which a power station will be built. Each number should be from $ 1 $ to $ n $ and all numbers should be pairwise distinct. You can print the numbers in arbitrary order.

After that, print an integer $ e $ — the number of connections to be made.

Finally, print $ e $ pairs of integers $ a $ and $ b $ ( $ 1 a, b n $ , $ a b $ ), denoting that a connection between City $ a $ and City $ b $ will be made. Each unordered pair of cities should be included at most once (for each $ (a, b) $ there should be no more $ (a, b) $ or $ (b, a) $ pairs). You can print the pairs in arbitrary order.

If there are multiple ways to choose the cities and the connections to obtain the construction of minimum price, then print any of them.

样例 #1

样例输入 #1

1
2
3
4
5
6
3
2 3
1 1
3 2
3 2 3
3 2 3

样例输出 #1

1
2
3
4
8
3
1 2 3
0

样例 #2

样例输入 #2

1
2
3
4
5
6
3
2 1
1 2
3 3
23 2 23
3 2 3

样例输出 #2

1
2
3
4
5
6
27
1
2
2
1 2
2 3

提示

For the answers given in the samples, refer to the following diagrams (cities with power stations are colored green, other cities are colored blue, and wires are colored red):

For the first example, the cost of building power stations in all cities is $ 3 + 2 + 3 = 8 $ . It can be shown that no configuration costs less than 8 yen.

For the second example, the cost of building a power station in City 2 is 2. The cost of connecting City 1 and City 2 is $ 2 (3 + 2) = 10 $ . The cost of connecting City 2 and City 3 is $ 3 (2 + 3) = 15 $ . Thus the total cost is $ 2 + 10 + 15 = 27 $ . It can be shown that no configuration costs less than 27 yen.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
// LUOGU_RID: 149085923
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
#define rep(i, a, n) for (int i = a; i <= n; i++)
#define frep(i, a, n) for (int i = a; i >= n; i--)
typedef pair<ll,ll> pll;
//一个pair
typedef pair<ll,pll> plll;
//pair套pair
const int N=1e6+100;
ll c[N];//造价
ll k[N];//这个是电缆代价
ll x[N];
ll y[N];
ll n;
//这个是横纵坐标
//要求曼哈顿距离用的
vector<pll>q[N];
ll dis[N];
//prim算法所必需的一个距离数组
//用来更新的,当权值比这个小的时候
//这时候我们需要一个优先队列了
//思考一下需要存什么
//距离必不可少,谁到谁必不可少
//因此结果已经出来了
bool vis[N];
//判断是否已经走过了
ll cnt,cot;
ll tot;
struct
{
ll u,v;
ll w;
}ans[N];
bool choose[N];
priority_queue<plll,vector<plll>,greater<plll> >p;
void prim(int s)
{
//为了尽量美观
//我们约定好{0,{0,s}};
//0是距离,0是起点,s是到达的点
//我们如此这般确定下来
rep(i,1,n)
{
dis[i]=1e18;
//赋值一个极其大的数字
}
dis[s]=0;
p.push({0,{0,s}});
//按照我们所约定的进行
while(!p.empty())
{
plll temp=p.top();
p.pop();
ll u=temp.second.second;
if(vis[u])
{
continue;
}
tot+=dis[u];
//累计我们第一个要求解的量
//就是花费
vis[u]=1;
//记录
ans[++cnt]={temp.second.first,temp.second.second,dis[u]};
if(temp.second.first==0)
{
choose[u]=1;
//就是选择这个点作为发电站的意思
//因为起点是0那边过来的
++cot;
//用来记录选为发电站的数量
}
if(cnt>=n+1)
{
return ;
//你已经存了n+1个答案,已经可以有逃出去了兄弟
}
pll x;
for(auto x:q[u])
{
int v=x.first;
ll w=x.second;
if(dis[v]>w)
{
dis[v]=w;
p.push({dis[v],{u,v}});
}
}
}
}
int shuliang;
int main()
{
cin>>n;
rep(i,1,n)
{
cin>>x[i]>>y[i];
//坐标读入
}
rep(i,1,n)
{
cin>>c[i];
//需要建立一个超级源点,和P1150一样,然后把距离和点扔进去
//并且需要表示0-i的情况,还有c[i]
q[0].push_back({i,c[i]});
//就是表示0-i,还有c[i]这个权值
}
rep(i,1,n)
{
cin>>k[i];
rep(j,1,i-1)
{
ll sum = abs(x[i]- x[j]) + abs(y[i] - y[j]);
q[i].push_back({j,(k[i]+k[j])*sum});
q[j].push_back({i, (k[i] + k[j]) * sum});
// 表示i-j,并且权值为曼哈顿距离乘k[i]
}
}
//已经做好了存边的操作,接下来1可以进行Prim算法了
//在此之前确认一下计算的量
//需要计算花费,需要计算发电站数量(要减去基站),需要打印出建造发电站的城市
//需要计算连接数量
//需要知道谁和谁连接
//看来还真不少,,接下来进入prim环节
prim(0);
//从超级源点出发
cout<<tot<<'\n'<<cot-1;
cout<<'\n';
//注意减去超级源点
rep(i,1,n)
{
if(choose[i])
{
cout<<i<<' ';
}
}
//如果是被选为工厂,就输出
cout<<'\n';
//输出换行
rep(i,1,cnt)
{
if(ans[i].u!=0&&ans[i].v!=0)
{
//如果不是来自超级源点或者到超级源点去,就是要连接的
//我们统计连接的数量33
shuliang++;
}
}
cout<<shuliang<<'\n';
//输出数量
rep(i, 1, cnt)
{
if (ans[i].u != 0 && ans[i].v != 0)
{
cout<<ans[i].u<<' '<<ans[i].v<<'\n';
}
}
//同理,我们输出连接的两个端点
}

舍友的一份代码:

也附上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// LUOGU_RID: 149090588
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
typedef pair<ll, pair<ll, ll>> plii;
const int MAXN = 1e5 + 10;
const ll inf = 1e18;
struct city
{
int x, y, c, k;
} a[MAXN];
struct Edge
{
int from, to;
ll val;
} ans[MAXN];
vector<Edge> edges[MAXN];
priority_queue<plii, vector<plii>, greater<plii>> q;
ll dis[MAXN], used[MAXN], book[MAXN], ans_val, cnt_build;
void add(int from, int to, ll val)
{
Edge e = {from, to, val};
edges[from].push_back(e);
}
ll calculate_distance(int x1, int y1, int x2, int y2, int k1, int k2)
{
return (1ll)*(k1 + k2) * (abs(x1 - x2) + abs(y1 - y2));
}
inline void prim_heap(int n)
{
for (int i = 0; i <= n; i++)
{
dis[i] = inf;
}
int tot = 0;
q.push({0, {0, 0}});
while (q.size() && tot < n + 1)
{
int now = q.top().second.second;
int now_from = q.top().second.first;
ll val = q.top().first;
q.pop();
if (used[now])
continue;
used[now] = 1;
tot++;
ans[tot] = {now_from, now, val};
ans_val += val;
if (now_from == 0)
{
book[now] = 1;
cnt_build++;
}
for (auto e : edges[now])
{
if (!used[e.to] && e.val < dis[e.to])
{
dis[e.to] = e.val;
q.push({e.val, {e.from, e.to}});
}
}
}
cout << ans_val << "\n";
cout << cnt_build - 1 << "\n";
for (int i = 1; i <= n; i++)
{
if (book[i])
cout << i << " ";
}
cout << "\n";
cout << tot - cnt_build << "\n";
if (tot - cnt_build != 0)
{
for (int i = 1; i <= tot; i++)
{
if (ans[i].from != 0 && ans[i].to != 0)
{
cout << ans[i].from << " " << ans[i].to << "\n";
}
}
}
}
int main()
{
ios::sync_with_stdio(0);
int n;
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i].x >> a[i].y;
}C++
for (int i = 1; i <= n; i++)
{
cin >> a[i].c;
add(0, i, a[i].c);
}
for (int i = 1; i <= n; i++)
{
cin >> a[i].k;
for (int j = 1; j < i; j++)
{
ll dis = calculate_distance(a[i].x, a[i].y, a[j].x, a[j].y, a[i].k, a[j].k);
add(i, j, dis);
add(j, i, dis);
}
}
prim_heap(n);
return 0;
}

[APIO2008] 免费道路

题目描述

新亚(New Asia)王国有 N 个村庄,由 M 条道路连接。其中一些道路是鹅卵石路,而其它道路是水泥路。保持道路免费运行需要一大笔费用,并且看上去 王国不可能保持所有道路免费。为此亟待制定一个新的道路维护计划。

国王已决定保持尽可能少的道路免费,但是两个不同的村庄之间都应该一条且仅由一条 且仅由一条免费道路的路径连接。同时,虽然水泥路更适合现代交通的需 要,但国王也认为走在鹅卵石路上是一件有趣的事情。所以,国王决定保持刚好 K 条鹅卵石路免费。

举例来说,假定新亚王国的村庄和道路如图 3(a)所示。如果国王希望保持两 条鹅卵石路免费,那么可以如图 3(b)中那样保持道路(1, 2)、(2, 3)、(3, 4)和(3, 5) 免费。该方案满足了国王的要求,因为:(1)两个村庄之间都有一条由免费道 路组成的路径;(2)免费的道路已尽可能少;(3)方案中刚好有两条鹅卵石道路 (2, 3)和(3, 4)

图 3: (a)新亚王国中村庄和道路的一个示例。实线标注的是水泥路,虚线标注 的是鹅卵石路。(b)一个保持两条鹅卵石路免费的维护方案。图中仅标出了免 费道路。

给定一个关于新亚王国村庄和道路的述以及国王决定保持免费的鹅卵石 道路数目,写一个程序确定是否存在一个道路维护计划以满足国王的要求,如果 存在则任意输出一个方案。

输入格式

输入第一行包含三个由空格隔开的整数:

N,村庄的数目(1≤N≤20,000);

M,道路的数目(1≤M≤100,000);

K,国王希望保持免费的鹅卵石道路数目(0≤K≤N - 1)。

此后 M 行述了新亚王国的道路,编号分别为 1 到 M。第(i+1)行述了第 i 条 道路的情况。用 3 个由空格隔开的整数述:

ui 和 vi,为第 i 条道路连接的两个村庄的编号,村庄编号为 1 到 N;

ci,表示第 i 条道路的类型。ci = 0 表示第 i 条道路是鹅卵石路,ci = 1 表 示第 i 条道路是水泥路。

输入数据保证一对村庄之间至多有一条道路连接

输出格式

如果满足国王要求的道路维护方案不存在,你的程序应该在输出第一行打印 no solution。 否则,你的程序应该输出一个符合要求的道路维护方案,也就是保持免费的 道路列表。按照输入中给定的那样输出免费的道路。如果有多种合法方案,你可 以任意输出一种。

样例 #1

样例输入 #1

1
2
3
4
5
6
7
8
5 7 2 
1 3 0
4 5 1
3 2 0
5 3 1
4 3 0
1 2 1
4 2 1

样例输出 #1

1
2
3
4
3 2 0 
4 3 0
5 3 1
1 2 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#include <bits/stdc++.h>
using ll = long long;
#define rep(i, a, b) for (int i = a; i <= b; i++)
#define frep(i, a, b) for (int i = a; i >= b; i--)
using namespace std;
const int MX = 100010;
int N, M, k, aa = 0, es = 0, fa[MX], cnt = 0;
struct Edge
{
int x, y;
};
// 存边
struct node
{
int x, y, z;
} Ans[MX];
// 存答案
Edge wa[MX], er[MX];
// 水泥,鹅卵石
bool vis[MX];
// 判断
int find(int x)
{
if (fa[x] == x)
return x;
return fa[x] = find(fa[x]);
}
// 并查集判断
void krus1()
{
memset(vis, 0, sizeof vis);
// 清空
// 解释一下这一编最小生成树的意义
//找到那些必须加入的0边
int tot = 0, num = 0;
// 开始跑
rep(i, 1, N)
fa[i] = i;
// 初始化
rep(i, 1, aa)
{
int a = find(wa[i].x);
int b = find(wa[i].y);
if (a != b)
{
fa[a] = b;
// 赋值
tot++;
// 加水泥路
}
}
rep(i, 1, es)
{
int a = find(er[i].x);
int b = find(er[i].y);
if (a != b)
{
vis[i] = 1;
tot++;
// 继续加鹅卵石路
num++;
// 记录数量
fa[a] = b;
// 赋值
if (tot == N - 1)
break;
// 够了,休息罢
}
}
if (tot < N - 1 || num > k)
{
// 不能加到N-1条或者要加的鹅卵石路很多,直接走
puts("no solution");
exit(0);
}
return;
}
//这一遍则是找到k条相邻的边后,进行找水泥路
void krus2()
{
int tot = 0;
rep(i, 1, N)
fa[i] = i;
rep(i, 1, es)
{
if (vis[i] == 0)
continue;
if (tot == k)
break;
int a = find(er[i].x);
int b = find(er[i].y);
if (a != b)
{
Ans[++cnt] = (node){er[i].x, er[i].y, 0};
tot++;
fa[a] = b;
}
}
// 尽可能多的加水泥路
rep(i, 1, es)
{
if (tot == k)
break;
if (vis[i] == 1)
continue;
int a = find(er[i].x);
int b = find(er[i].y);
if (a != b)
{
Ans[++cnt] = (node){er[i].x, er[i].y, 0};
fa[a] = b;
tot++;
}
}
// 一定要到k条
if (tot < k)
{
cout << "no solution";
exit(0);
}
rep(i, 1, aa)
{
int a = find(wa[i].x);
int b = find(wa[i].y);
if (a != b)
{
tot++;
fa[a] = b;
Ans[++cnt] = (node){wa[i].x, wa[i].y, 1};
}
}
// 开始加上鹅暖石路
return;
}
int main()
{
cin >> N >> M >> k;
rep(i, 1, M)
{
int u, v, w;
cin >> u >> v >> w;
if (w == 1)
wa[++aa].x = u, wa[aa].y = v;
else
er[++es].x = u, er[es].y = v;
}
krus1();
krus2();
rep(i, 1, cnt)
cout
<< Ans[i].x << " " << Ans[i].y << ' ' << Ans[i].z << '\n';
return 0;
}