P3366 【模板】最小生成树
题目描述
如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出 orz。
输入格式
第一行包含两个整数 N,M,表示该图共有 N 个结点和 M 条无向边。
接下来 M 行每行包含三个整数 Xi,Yi,Zi,表示有一条长度为 Zi 的无向边连接结点 Xi,Yi。
输出格式
如果该图连通,则输出一个整数表示最小生成树的各边的长度之和。如果该图不连通则输出 orz。
输入输出样例
输入 #1复制
4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3
输出 #1复制
7
说明/提示
数据规模:
对于 20% 的数据,N≤5,M≤20。
对于 40% 的数据,N≤50,M≤2500。
对于 70% 的数据,N≤500,M≤104。
对于 100% 的数据:1≤N≤5000,1≤M≤2×105,1≤Zi≤104,1≤Xi,Yi≤N。
样例解释:

所以最小生成树的总边权为 2+2+3=7。
实现代码:
cpp
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int MaxN = 5000 + 5, MaxM = 200000 + 5;
int N, M;
int U[MaxM], V[MaxM], W[MaxM];
bool used[MaxM];
int par[MaxN], Best[MaxN];
void init() {
scanf("%d %d", &N, &M);
for (int i = 1; i <= M; ++i)
scanf("%d %d %d", &U[i], &V[i], &W[i]);
}
void init_dsu() {
for (int i = 1; i <= N; ++i)
par[i] = i;
}
int get_par(int x) {
if (x == par[x]) return x;
else return par[x] = get_par(par[x]);
}
inline bool Better(int x, int y) {
if (y == 0) return true;
if (W[x] != W[y]) return W[x] < W[y];
return x < y;
}
void Boruvka() {
init_dsu();
int merged = 0, sum = 0;
bool update = true;
while (update) {
update = false;
memset(Best, 0, sizeof Best);
for (int i = 1; i <= M; ++i) {
if (used[i] == true) continue;
int p = get_par(U[i]), q = get_par(V[i]);
if (p == q) continue;
if (Better(i, Best[p]) == true) Best[p] = i;
if (Better(i, Best[q]) == true) Best[q] = i;
}
for (int i = 1; i <= N; ++i)
if (Best[i] != 0 && used[Best[i]] == false) {
update = true;
merged++; sum += W[Best[i]];
used[Best[i]] = true;
par[get_par(U[Best[i]])] = get_par(V[Best[i]]);
}
}
if (merged == N - 1) printf("%d\n", sum);
else puts("orz");
}
int main() {
init();
Boruvka();
return 0;
}
P1194 买礼物
题目描述
又到了一年一度的明明生日了,明明想要买 B 样东西,巧的是,这 B 样东西价格都是 A 元。
但是,商店老板说最近有促销活动,也就是:
如果你买了第 I 样东西,再买第 J 样,那么就可以只花 KI,J 元,更巧的是,KI,J 竟然等于 KJ,I。
现在明明想知道,他最少要花多少钱。
输入格式
第一行两个整数,A,B。
接下来 B 行,每行 B 个数,第 I 行第 J 个为 KI,J。
我们保证 KI,J=KJ,I 并且 KI,I=0。
特别的,如果 KI,J=0,那么表示这两样东西之间不会导致优惠。
注意 KI,J 可能大于 A。
输出格式
一个整数,为最小要花的钱数。
输入输出样例
输入 #1复制
1 1
0
输出 #1复制
1
输入 #2复制
3 3
0 2 4
2 0 2
4 2 0
输出 #2复制
7
说明/提示
样例解释 2。
先买第 2 样东西,花费 3 元,接下来因为优惠,买 1,3 样都只要 2 元,共 7 元。
(同时满足多个"优惠"的时候,聪明的明明当然不会选择用 4 元买剩下那件,而选择用 2 元。)
数据规模
对于 30% 的数据,1≤B≤10。
对于 100% 的数据,1≤B≤500,0≤A,KI,J≤1000。
2018.7.25新添数据一组
实现代码:
cpp
#include<bits/stdc++.h>
using namespace std;
struct node {
int x,y,s;
} d[200000];
int n,m,ans,num,f[1000],w,k,c;
bool ok[1000];
bool cmp(node a,node b) {
return a.s<b.s;
}
int gf(int x) {
if(f[x]==x)return x;
return f[x]=gf(f[x]);
}
void work() {
int x,y;
for(int i=1; i<=n; i++)f[i]=i;
for(int i=1; i<=k-num; i++) {
x=gf(d[i].x);
y=gf(d[i].y);
if(x!=y) {
f[x]=y;
c++;
ans+=d[i].s;
}
}
}
int main() {
cin>>m>>n;
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++) {
cin>>w;
if(i>=j||w==0)continue;
d[++k].x=i;
d[k].y=j;
d[k].s=w;
if(d[k].s>m)num++;
}
sort(d+1,d+k+1,cmp);
work();
if(c==n-1)cout<<ans+m;
else cout<<ans+(n-c)*m;
return 0;
}
P4180 [BJWC2010] 严格次小生成树
题目描述
小 C 最近学了很多最小生成树的算法,Prim 算法、Kruskal 算法、消圈算法等等。正当小 C 洋洋得意之时,小 P 又来泼小 C 冷水了。小 P 说,让小 C 求出一个无向图的次小生成树,而且这个次小生成树还得是严格次小的,也就是说:如果最小生成树选择的边集是 EM,严格次小生成树选择的边集是 ES,那么需要满足:(value(e) 表示边 e 的权值) ∑e∈EMvalue(e)<∑e∈ESvalue(e)。
这下小 C 蒙了,他找到了你,希望你帮他解决这个问题。
输入格式
第一行包含两个整数 N 和 M,表示无向图的点数与边数。
接下来 M 行,每行 3 个数 x,y,z 表示,点 x 和点 y 之间有一条边,边的权值为 z。
输出格式
包含一行,仅一个数,表示严格次小生成树的边权和。
输入输出样例
输入 #1复制
5 6
1 2 1
1 3 2
2 4 3
3 5 4
3 4 3
4 5 6
输出 #1复制
11
说明/提示
数据中无向图不保证无自环。
对于 50% 的数据, N≤2000,M≤3000。
对于 80% 的数据, N≤5×104,M≤105。
对于 100% 的数据, N≤105,M≤3×105,边权 ∈[0,109],数据保证必定存在严格次小生成树。
实现代码:
cpp
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <set>
#include <map>
#include <queue>
using namespace std;
typedef long long ll;
const ll INF = (1ll << 62);
#define DEBUG(x) std::cerr << #x << " = " << x << std::endl
#define int ll
inline ll read() {
ll f = 1, x = 0;char ch;
do {ch = getchar();if (ch == '-')f = -1;} while (ch > '9' || ch < '0');
do {x = x * 10 + ch - '0';ch = getchar();} while (ch >= '0' && ch <= '9');
return f * x;
}
const int MAX_N = 100000 + 50;
const int MAX_M = 300000 + 50;
const int MAX_F = 30 + 5;
struct EDGE {
int to, next, w;
} edge[MAX_M << 1];
int head[MAX_N], cnt;
void addedge (int u, int v, int w) {
edge[++cnt].to = v;
edge[cnt].w = w;
edge[cnt].next = head[u];
head[u] = cnt;
}
int f[MAX_N][MAX_F], g[MAX_N][MAX_F], h[MAX_N][MAX_F], dep[MAX_N];
inline void ckmax (int &x, int y) {
x = (x > y ? x : y);
}
inline void dfs (int u, int fa, int w) {
dep[u] = dep[fa] + 1;
f[u][0] = fa;
g[u][0] = w;
h[u][0] = -INF;
for (int i = 1; i <= 20; i ++ ) {
f[u][i] = f[f[u][i - 1]][i - 1];
g[u][i] = max (g[u][i - 1], g[f[u][i - 1]][i - 1]);
h[u][i] = max (h[u][i - 1], h[f[u][i - 1]][i - 1]);
if (g[u][i - 1] > g[f[u][i - 1]][i - 1]) h[u][i] = max (h[u][i], g[f[u][i - 1]][i - 1]);
else if (g[u][i - 1] < g[f[u][i - 1]][i - 1]) h[u][i] = max (h[u][i], g[u][i - 1]);
}
for (int i = head[u]; i; i = edge[i].next ) {
int v = edge[i].to, w = edge[i].w;
if (v == fa) continue;
dfs (v, u, w);
}
}
inline int LCA (int x, int y) {
if (dep[x] < dep[y]) swap (x, y);
for (int i = 20; i >= 0; i -- ) {
if (dep[f[x][i]] >= dep[y]) x = f[x][i];
}
if (x == y) return x;
for (int i = 20; i >= 0; i -- ) {
if (f[x][i] != f[y][i]) {
x = f[x][i];
y = f[y][i];
}
}
return f[x][0];
}
int n, m, fa[MAX_N], res, sum;
struct Node {
int u, v, w;
bool operator < (const Node & rhs ) const {
return w < rhs.w;
}
} a[MAX_M << 1];
inline int find (int x) {
return x == fa[x] ? x : fa[x] = find (fa[x]);
}
inline void merge (int x, int y) {
x = find (x); y = find (y);
if (x == y) return;
fa[x] = y;
}
bool vis[MAX_M];
inline void kruskal () {
n = read (); m = read ();
for (int i = 1; i <= m; i ++ ) {
a[i].u = read (); a[i].v = read (); a[i].w = read ();
}
sort (a + 1, a + m + 1);
for (int i = 1; i <= n; i ++ ) fa[i] = i;
res = 0;
for (int i = 1; i <= m; i ++ ) {
if (find (a[i].u) != find (a[i].v)) {
vis[i] = 1;
res ++ ;
merge (a[i].u, a[i].v);
sum += a[i].w;
addedge (a[i].u, a[i].v, a[i].w);
addedge (a[i].v, a[i].u, a[i].w);
}
if (res == n - 1) break;
}
res = 0;
dfs (1, 0, 0);
}
inline int qmax (int u, int v, int maxx) {
int ans = -INF;
for (int i = 18; i >= 0; i -- ) {
if (dep[f[u][i]] >= dep[v]) {
if (maxx != g[u][i]) ans = max (ans, g[u][i]);
else ans = max (ans, h[u][i]);
u = f[u][i];
}
}
return ans;
}
inline void ckmin (int &x, int y) {
x = (x < y ? x : y);
}
inline void calc () {
int ans = INF;
for (int i = 1; i <= m; i ++ ) {
if (vis[i]) continue;
int lca = LCA (a[i].u, a[i].v);
int max_u = qmax (a[i].u, lca, a[i].w);
int max_v = qmax (a[i].v, lca, a[i].w);
ckmin (ans, sum - max (max_u, max_v) + a[i].w);
}
printf ("%lld\n", ans);
}
signed main() {
kruskal ();
calc ();
return 0;
}
P1396 营救
题目背景
"咚咚咚......""查水表!"原来是查水表来了,现在哪里找这么热心上门的查表员啊!小明感动得热泪盈眶,开起了门......
题目描述
妈妈下班回家,街坊邻居说小明被一群陌生人强行押上了警车!妈妈丰富的经验告诉她小明被带到了 t 区,而自己在 s 区。
该市有 m 条大道连接 n 个区,一条大道将两个区相连接,每个大道有一个拥挤度。小明的妈妈虽然很着急,但是不愿意拥挤的人潮冲乱了她优雅的步伐。所以请你帮她规划一条从 s 至 t 的路线,使得经过道路的拥挤度最大值最小。
输入格式
第一行有四个用空格隔开的 n,m,s,t,其含义见【题目描述】。
接下来 m 行,每行三个整数 u,v,w,表示有一条大道连接区 u 和区 v,且拥挤度为 w。
两个区之间可能存在多条大道。
输出格式
输出一行一个整数,代表最大的拥挤度。
输入输出样例
输入 #1复制
3 3 1 3
1 2 2
2 3 1
1 3 3
输出 #1复制
2
说明/提示
数据规模与约定
- 对于 30% 的数据,保证 n≤10。
- 对于 60% 的数据,保证 n≤100。
- 对于 100% 的数据,保证 1≤n≤104,1≤m≤2×104,w≤104,1≤s,t≤n。且从 s 出发一定能到达 t 区。
样例输入输出 1 解释
小明的妈妈要从 1 号点去 3 号点,最优路线为 1→2→3。
实现代码:
cpp
#include<bits/stdc++.h>
using namespace std;
int n,m,s,t,a[20001];
struct each
{
int x,y,cost;
}b[20001];
bool com(each x,each y)
{
return x.cost<y.cost;
}
int read(){
char ch=getchar();
int x=0,f=1;
while(ch<'0'||ch>'9')
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
int find(int x){
if(a[x]==0)
return x;
a[x]=find(a[x]);
return a[x];
}
int main(){
n=read();
m=read();
s=read();
t=read();
for(int i=1;i<=m;i++)
{
b[i].x=read();
b[i].y=read();
b[i].cost=read();
}
sort(b+1,b+m+1,com);
for(int i=1;i<=m;i++)
{
int X=find(b[i].x),Y=find(b[i].y);
if(X!=Y)
a[X]=Y;
if(find(s)==find(t))
{
cout<<b[i].cost<<endl;
return 0;
}
}
return 0;
}
P1195 口袋的天空
题目背景
小杉坐在教室里,透过口袋一样的窗户看口袋一样的天空。
有很多云飘在那里,看起来很漂亮,小杉想摘下那样美的几朵云,做成棉花糖。
题目描述
给你云朵的个数 N,再给你 M 个关系,表示哪些云朵可以连在一起。
现在小杉要把所有云朵连成 K 个棉花糖,一个棉花糖最少要用掉一朵云,小杉想知道他怎么连,花费的代价最小。
输入格式
第一行有三个数 N,M,K。
接下来 M 行每行三个数 X,Y,L,表示 X 云和 Y 云可以通过 L 的代价连在一起。
输出格式
对每组数据输出一行,仅有一个整数,表示最小的代价。
如果怎么连都连不出 K 个棉花糖,请输出 No Answer。
输入输出样例
输入 #1复制
3 1 2
1 2 1
输出 #1复制
1
说明/提示
对于 30% 的数据,1≤N≤100,1≤M≤103;
对于 100% 的数据,1≤N≤103,1≤M≤104,1≤K≤10,1≤X,Y≤N,0≤L<104。
实现代码:
cpp
#include<bits/stdc++.h>
using namespace std;
int n,k,m;
int fa[1000050];
struct node {
int x;
int y;
int l;
} a[1000005];
int cmp( const void *a , const void *b ) {
struct node *c = (node *)a;
struct node *d = (node *)b;
return c->l - d->l;
}
int find(int x){
if(x!=fa[x])
fa[x]=find(fa[x]);
return fa[x];
}
void work(int x,int y){
x=find(x);
y=find(y);
if(x==y)
return;
fa[x]=y;
}
int main(){
cin>>n>>m>>k;
for(int i=0; i<m; i++) {
scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].l);
}
qsort(a,m,sizeof(a[0]),cmp);
for(int i=1;i<=n;i++)
fa[i]=i;
int num=n-k;
int ans=0;
for(int i=0;i<m;i++)
{
if(num==0)
break;
int aaa=find(a[i].x);
int wzx=find(a[i].y);
if(aaa!=wzx)
{
work(a[i].x,a[i].y);
ans+=a[i].l;
num--;
}
}
if(num)
cout<<"No Answer"<<endl;
else
cout<<ans<<endl;
}