浅谈基础的图算法——最小生成树算法(c++)

文章目录

前置知识

(graph) 是一个二元组 G = ( V ( G ) , E ( G ) ) G=(V(G),E(G)) G=(V(G),E(G))。其中V(G)是非空集,称为点集(vertex set),对于 V V V中的每个元素,我们称其为顶点 (vertex) 或 节点 (node),简称 E ( G ) E(G) E(G) V ( G ) V(G) V(G)各结点之间边的集合,称为 边集 (edge set)。
常用 G = ( V ( G ) , E ( G ) ) G=(V(G),E(G)) G=(V(G),E(G))表示图。
图有多种,包括 无向图 (undirected graph),有向图 (directed graph),混合图 (mixed graph) 等。

存图方法

邻接矩阵

最最简单的存图方式,本身很好想也很好实现,Floyd中有所应用

邻接链表(链式前向星)(网络流必备)

int ne[N], h[N], to[N], w[N], tot;
inline void add(int x, int y, int z) {
ne[++tot] = h[x], to[h[x] = tot] = y, w[tot] = z;
}
//add(x, y, w);

vector(简单,快)

vector<int> ed[N];
ed[x].push_back(y);
for (auto y: ed[x]) {
//do something with y
}

最小生成树

最小生成树之前有写博客讲解,这次再讲一遍
戳这里
求连通图中边权和/边权最大值最小的一个生成树

模板题

【模板】最小生成树

题目描述

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

输入格式

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

接下来 M M M 行每行包含三个整数 X i , Y i , Z i X_i,Y_i,Z_i Xi,Yi,Zi,表示有一条长度为 Z i Z_i Zi 的无向边连接结点 X i , Y i X_i,Y_i Xi,Yi

输出格式

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

样例 #1
样例输入 #1
4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3
样例输出 #1
7
提示

数据规模:

对于 20 % 20\% 20% 的数据, N ≤ 5 N\le 5 N5 M ≤ 20 M\le 20 M20

对于 40 % 40\% 40% 的数据, N ≤ 50 N\le 50 N50 M ≤ 2500 M\le 2500 M2500

对于 70 % 70\% 70% 的数据, N ≤ 500 N\le 500 N500 M ≤ 1 0 4 M\le 10^4 M104

对于 100 % 100\% 100% 的数据: 1 ≤ N ≤ 5000 1\le N\le 5000 1N5000 1 ≤ M ≤ 2 × 1 0 5 1\le M\le 2\times 10^5 1M2×105 1 ≤ Z i ≤ 1 0 4 1\le Z_i \le 10^4 1Zi104

样例解释:

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

AC代码

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
using namespace std;

const int M=300100;
struct node{
    int x;
    int y;
    int w;
}a[M],t;
int n,e,fa[M],p=1,ans;

inline bool cmp(node x,node y){
    if(x.w<y.w)return 1;
  //  if(x.w==y.w)
  //      if(x.x>y.x)return 1;
    return 0;
}

inline void qsort(int l,int r){
    int i=l,j=r;node mid=a[(i+j)/2];
    while(i<=j){
        while(cmp(a[i],mid))i++;
        while(cmp(mid,a[j]))j--;
        if(i<=j){
            t=a[i];a[i]=a[j];a[j]=t;
            i++;j--;
        }
    }
    if(i<r)qsort(i,r);
    if(l<j)qsort(l,j);
}
inline int Find(int x){
    if(x==fa[x])return x;
    fa[x]=Find(fa[x]);
    return fa[x];
}
inline void solve(){
    qsort(1,e);
    for(int i=1;i<=n;i++)fa[i]=i;
    for(int i=1;i<=e;i++){
        if(Find(a[i].x)!=Find(a[i].y)){
            ans+=a[i].w;
            fa[Find(a[i].x)]=a[i].y;
            p++;
            if(p==n)return ;
        }
    }
}
int main(){
    scanf("%d%d",&n,&e);
    for(int i=1;i<=e;i++)scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].w);
    solve();
    int fat=Find(1);
    for(int i=1;i<=n;i++){
    	if(fat!=Find(i)){
    		cout<<"orz"<<endl;
    		return 0;
    	}
    }
    printf("%d",ans);
    return 0;
}

定义

我们定义无向连通图的 最小生成树(Minimum Spanning Tree,MST)为边权和最小的生成树。
注意:只有连通图才有生成树,而对于非连通图,只存在生成森林。

Kruskal 算法

Kruskal 算法是一种常见并且好写的最小生成树算法,由 Kruskal 发明。该算法的基本思
想是从小到大加入边,是个贪心算法。
详细来说,每一次找到边权最小的一条边,一旦找到的边加入后,图不再是一个树,那就舍弃他,否则保留它,不难证明,这种算法能生成最小生成树。

实现

算法虽简单,但需要相应的数据结构来支持……具体来说,维护一个森林,查询两个结点是否在同一棵树中,连接两棵树。
抽象一点地说,维护一堆 集合,查询两个元素是否属于同一集合,合并两个集合。
其中,查询两点是否连通和连接两点可以使用并查集维护。
如果使用 O ( m l o g m ) O(mlogm) O(mlogm)的排序算法,并且使用 O ( m a ( m , n ) ) O(ma(m,n)) O(ma(m,n)) O ( m l o g m ) O(mlogm) O(mlogm)的并查集,就可以得到时间复杂度为 O ( m l o g m ) O(mlogm) O(mlogm)的 Kruskal 算法。

代码

for (int i = 0;i < m; i++)
scanf("%d %d %d",&g[i].a,&g[i].b,&g[i].z);
sort (g, g + m, cmp);
for (int i = 0;i < m; i++) {
if (!check(g[i].a, g[i].b)) {
un(g[i].a, g[i].b);
ans += g[i].z;
cnt++;
}
if (cnt == n - 1) break;
}
if (cnt == n - 1) cout << ans;
else cout << "orz";

证明

思路很简单,为了造出一棵最小生成树,我们从最小边权的边开始,按边权从小到大依次加入,如果某次加边产生了环,就扔掉这条边,直到加入了 n − 1 n-1 n1条边,即形成了一棵树。
证明:
使用归纳法,证明任何时候 K 算法选择的边集都被某棵 MST 所包含。
基础:对于算法刚开始时,显然成立(最小生成树存在)。
归纳:假设某时刻成立,当前边集为 F F F,令 T T T为这棵 MST,考虑下一条加入的边 e e e
如果 e e e属于 T T T,那么成立。
否则, e + T e+T e+T一定存在一个环,考虑这个环上不属于 的另一条边 (一定只有一条)。
首先, f f f的权值一定不会比 e e e小,不然 f f f会在 e e e之前被选取。
然后, f f f的权值一定不会比 e e e大,不然 T + e − f T+e-f T+ef就是一棵比 T T T还优的生成树了。
所以, T + e − f T+e-f T+ef包含了 F F F,并且也是一棵最小生成树,归纳成立

Prim 算法

Prim 算法是另一种常见并且好写的最小生成树算法。该算法的基本思想是从一个结点开
始,不断加点(而不是 Kruskal 算法的加边)。

实现

具体来说,每次要选择距离最的一个结点,以及用新的边更新其他结点的距离。其实跟 Dijkstra 算法一样,每次找到距离最小的一个点,可以暴力找也可以用堆维护。堆优化的方式类似 Dijkstra 的堆优化,但如果使用二叉堆等不支持 O ( 1 ) O(1) O(1)decrease-key的堆,复杂度就不优于 Kruskal,常数也比 Kruskal 大。所以,一般情况下都使用 Kruskal
算法,在稠密图尤其是完全图上,暴力 Prim 的复杂度比 Kruskal 优,但 不一定 实际跑得更快。
暴力: O ( n 2 + m ) O(n^2+m) O(n2+m)

代码

for (int i = 1;i <= m; i++) {
read(x), read(y), read(s);
a[x][y] = a[y][x] = min(s, a[x][y]);
}
d[1] = 0;
for (int i = 1;i < n; i++) {
x = 0;
for (int j = 1;j <= n; j++)
if (!v[j] && (x == 0 || d[j] < d[x])) x = j;
v[x] = 1;
for (int y = 1;y <= n; y++)
if (!v[y]) d[y] = min(d[y], a[x][y]);
}

证明

证明
从任意一个结点开始,将结点分成两类:已加入的,未加入的。
每次从未加入的结点中,找一个与已加入的结点之间边权最小值最小的结点。
然后将这个结点加入,并连上那条边权最小的边。
重复 n − 1 n-1 n1次即可。
证明:还是说明在每一步,都存在一棵最小生成树包含已选边集。
基础:只有一个结点的时候,显然成立。
归纳:如果某一步成立,当前边集为 F F F,属于 T T T这棵 MST,接下来要加入边 e e e
如果 e e e属于 T T T,那么成立。
否则考虑 T + e T+e T+e中环上另一条可以加入当前边集的边 f f f
首先, f f f的权值一定不小于 e e e的权值,否则就会选择 f f f而不是 e e e了。
然后, f f f的权值一定不大于 e e e的权值,否则 T + e − f T+e-f T+ef就是一棵更小的生成树了。
因此, e e e f f f的权值相等, T + e − f T+e-f T+ef也是一棵最小生成树,且包含了 F F F

例题讲解

营救

题目背景

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

题目描述

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

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

输入格式

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

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

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

输出格式

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

样例 #1
样例输入 #1
3 3 1 3
1 2 2
2 3 1
1 3 3
样例输出 #1
2
提示
数据规模与约定
  • 对于 30 % 30\% 30% 的数据,保证 n ≤ 10 n\leq 10 n10
  • 对于 60 % 60\% 60% 的数据,保证 n ≤ 100 n\leq 100 n100
  • 对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 1 0 4 1 \leq n\leq 10^4 1n104 1 ≤ m ≤ 2 × 1 0 4 1 \leq m \leq 2 \times 10^4 1m2×104 w ≤ 1 0 4 w \leq 10^4 w104 1 ≤ s , t ≤ n 1 \leq s, t \leq n 1s,tn。且从 s s s 出发一定能到达 t t t 区。

样例输入输出 1 解释

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

思路

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

AC代码

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
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 find(int x)
{
    if(a[x]==0)
        return x;
    a[x]=find(a[x]);
    return a[x];
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m>>s>>t;
    for(int i=1;i<=m;i++)
    	cin>>b[i].x>>b[i].y>>b[i].cost;
    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;
}

CF1245D Shichikuji and Power Grid(建图)

题面翻译

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

在一个城市建造发电站的代价是 c [ i ] c[i] c[i],将 i i i j j j 两个城市用电缆相连的代价是 k [ i ] + k [ j ] k[i]+k[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 \ne 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 \leq n \leq 2000 $ ) — the number of cities.

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

The next line contains $ n $ space-separated integers $ c_1, c_2, \dots, c_n $ ( $ 1 \leq c_i \leq 10^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, \dots, k_n $ ( $ 1 \leq k_i \leq 10^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 \le a, b \le n $ , $ a \ne 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
3
2 3
1 1
3 2
3 2 3
3 2 3
样例输出 #1
8
3
1 2 3 
0
样例 #2
样例输入 #2
3
2 1
1 2
3 3
23 2 23
3 2 3
样例输出 #2
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 \cdot (3 + 2) = 10 $ . The cost of connecting City 2 and City 3 is $ 3 \cdot (2 + 3) = 15 $ . Thus the total cost is $ 2 + 10 + 15 = 27 $ . It can be shown that no configuration costs less than 27 yen.

思路

新建一个虚点,和其相连表示新建电厂,跑最小生成树

AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, ll> pil;
typedef pair<ll, pair<int, int> > plii;
const int N = 2010;
int n, cnt, cot;
ll tot,x[N], y[N],c[N], k[N], dis[N];
bool vis[N], ok[N];
vector<pil> son[N];
priority_queue<plii, vector<plii>, greater<plii> > q; 
struct edge {
	int u, v;
	ll w;
} ans[N]; 
inline void prim(int s) {
	q.push({0, {s, 0}});
	for (int i = 0; i <= n; i++)
		dis[i] = 1e18;
	dis[s] = 0;
	while (! q.empty()) {
		plii it = q.top();
		q.pop();
		int u = it.second.first;
		if (vis[u])	continue;
		cnt++;
		tot += dis[u];
		vis[u] = 1;
		ans[cnt] = edge{it.second.second, it.second.first, dis[u]};
		if (it.second.second == 0) { 
			ok[u] = 1;
			cot++;
		}
		if (cnt >= n + 1)return ;
		for (auto it : son[u]) {
			int v = it.first;
			ll w = it.second;
			if (dis[v] > w) {
				dis[v] = w;
				q.push({dis[v], {v, u}});
			}
		}
	}
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
		scanf("%lld%lld", &x[i], &y[i]);
	for (int i = 1; i <= n; i++) {
		scanf("%lld", &c[i]);
		son[0].push_back({i, c[i]});
	}
	for (int i = 1; i <= n; i++) {
		scanf("%lld", &k[i]);
		for (int j = 1; j < i; ++ j) {
			ll dis = abs(x[i] - x[j]) + abs(y[i] - y[j]);
			dis = dis * (k[i] + k[j]);
			son[i].push_back({j, dis});
			son[j].push_back({i, dis});
		}
	}
	prim(0);
	printf("%lld\n%d\n", tot, cot - 1);

	for (int i = 1; i <= n; i++) {
		if (ok[i]) {
			printf("%d ", i);
		}
	}
	putchar('\n');
	int res = 0;
	for (int i = 1; i <= cnt; i++) {
		if (ans[i].u != 0 && ans[i].v != 0) {
			++ res;
		}
	}
	printf("%d\n", res);
	for (int i = 1; i <= cnt; i++) {
		if (ans[i].u != 0 && ans[i].v != 0) {
			if(ans[i].u>ans[i].v)swap(ans[i].u, ans[i].v);
			printf("%d %d\n", ans[i].u, ans[i].v);
		}
	}
	return 0;
}

P8074 [COCI2009-2010#7] SVEMIR(去除冗边)

题目描述

太空帝国要通过建造隧道来联通它的 N N N 个星球。

每个星球用三维坐标 ( x i , y i , z i ) (x_i,y_i,z_i) (xi,yi,zi) 来表示,而在两个星球 A , B A,B A,B 之间建造隧道的价格为 min ⁡ { ∣ x A − x B ∣ , ∣ y A − y B ∣ , ∣ z A − z B ∣ } \min\{|x_A-x_B|,|y_A-y_B|,|z_A-z_B|\} min{xAxB,yAyB,zAzB}

现要建造 N − 1 N-1 N1 条隧道使得所有的星球都能直接或间接相连。求完成该任务所需的最小总价。

输入格式

第一行,一个整数 N N N

接下来的 N N N 行,每行三个整数 x i , y i , z i x_i,y_i,z_i xi,yi,zi,表示第 i i i 个星球的坐标。

数据保证不存在两个具有相同坐标的星球。

输出格式

输出所需的最小总价。

样例 #1
样例输入 #1
2
1 5 10
7 8 2
样例输出 #1
3
样例 #2
样例输入 #2
3
-1 -1 -1
5 5 5
10 10 10
样例输出 #2
11
样例 #3
样例输入 #3
5
11 -15 -15
14 -5 -15
-1 -1 -5
10 -4 -1
19 -4 19
样例输出 #3
4
提示

【数据规模与约定】

  • 对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 1 0 5 1 \le N \le 10^5 1N105 − 1 0 9 ≤ x i , y i , z i ≤ 1 0 9 -10^9 \le x_i,y_i,z_i \le 10^9 109xi,yi,zi109

【提示与说明】

题目译自 COCI 2009-2010 CONTEST #7 Task 4 SVEMIR

本题分值按 COCI 原题设置,满分 100 100 100

思路

最小生成树,但代价比较特殊。
不妨设两个点的距离由 x 决定,代价为 ∣ x A − x B ∣ |x_A-x_B| xAxB
我们发现在数轴上 x A x_A xA x B x_B xB必定是相邻的,否则不如把邻居加进来。
所以我们只需 3n 条边,只考虑 x,y,z 相邻权值连边,跑最小生成树即可。

AC代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10,M=1e7+10;
int n,m,ans,k;
int fa[N];
struct Edge{
	int u,v,w;
}edge[M];
bool operator < (const Edge &a ,const Edge &b){
	return a.w<b.w;
}
struct Planet{
	int x,y,z,id;
	inline void putin(int i){
		scanf("%d%d%d",&x,&y,&z);
		id=i;
	}
}planet[N];
inline bool cmpx (Planet a, Planet b) {return a.x < b.x;}
inline bool cmpy (Planet a, Planet b) {return a.y < b.y;}
inline bool cmpz (Planet a, Planet b) {return a.z < b.z;}
inline int find(int x){
	if(x!=fa[x])fa[x]=find(fa[x]);
	return fa[x];
}
inline void add (Planet a, Planet b)
{
	edge[++m].u = a.id;edge[m].v = b.id;
	edge[m].w = min (abs (a.x - b.x), min (abs (a.y - b.y), abs (a.z - b.z))); 
}
void Kruska ()
{
	sort (edge + 1, edge + m + 1);
	for (int i = 1; i <= m; i++)
	{
		int r1 = find (edge[i].u), r2 = find (edge[i].v);
		if (r1 == r2)	continue;
		fa[r2] = r1;
		k++;ans += edge[i].w;
		if (k == n - 1)	return;
	}
}
int main(){
	scanf ("%d", &n);
	for (int i = 1; i <= n; i++)planet[i].putin (i);
	for (int i = 1; i <= n; i++)fa[i] = i;
	sort (planet + 1, planet + n + 1, cmpx);
	for (int i = 2; i <= n; i++)add (planet[i - 1], planet[i]);
	sort (planet + 1, planet + n + 1, cmpy);
	for (int i = 2; i <= n; i++)add (planet[i - 1], planet[i]);
	sort (planet + 1, planet + n + 1, cmpz);
	for (int i = 2; i <= n; i++)add (planet[i - 1], planet[i]);
	Kruska ();
	printf ("%d", ans);
	return 0;
}

P5994 [PA2014] Kuglarz(思维题)

题目描述

魔术师的桌子上有 n n n 个杯子排成一行,编号为 1 , 2 , … , n 1,2,…,n 1,2,,n,其中某些杯子底下藏有一个小球,如果你准确地猜出是哪些杯子,你就可以获得奖品。

花费 c i j c_{ij} cij 元,魔术师就会告诉你杯子 i , i + 1 , … , j i,i+1,…,j i,i+1,,j 底下藏有球的总数的奇偶性。

采取最优的询问策略,你至少需要花费多少元,才能保证猜出哪些杯子底下藏着球?

输入格式

第一行一个整数 n n n

i + 1 i+1 i+1 行( 1 ≤ i ≤ n 1\le i\le n 1in)有 n + 1 − i n+1-i n+1i 个整数,表示每一种询问所需的花费。

其中 c i j c_{ij} cij(对区间 [ i , j ] [i,j] [i,j] 进行询问的费用, 1 ≤ i ≤ j ≤ n 1\le i\le j\le n 1ijn)为第 i + 1 i+1 i+1 行第 j + 1 − i j+1-i j+1i 个数。

输出格式

输出一个整数,表示最少花费。

样例 #1
样例输入 #1
5
1 2 3 4 5
4 3 2 1
3 4 5
2 1
5
样例输出 #1
7
提示

对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 2 × 1 0 3 1\le n\le 2\times 10^3 1n2×103 1 ≤ c i j ≤ 1 0 9 1\le c_{ij}\le 10^9 1cij109

思路

第一步要做前缀和 S i S_i Si,那么每次询问相当于求 S j − S i − 1 S_j-S_{i-1} SjSi1的奇偶性,我们可以连一条
边。
也就是求出 S j S_j Sj和S_{i-1}的奇偶关系。
假设我们得到了最终答案,相当于我们知道了所有 S i S_i Si的奇偶性。而我们知道所有 S i S_i Si
奇偶性后,我们可以通过 S i − S i − 1 S_i-S_{i-1} SiSi1求出第 i 个位置的答案。
所以说原问题等价于求出所有 S i S_i Si的奇偶性,因为我们知道 S 0 S_0 S0是偶数,所以就是求最小
生成树!

AC代码
#include<bits/stdc++.h>
using namespace std;
template<class T>
inline void read(T &x)
{
	x=0;long long f=0;char ch=getchar();
	while(!isdigit(ch))	f=ch=='-',ch=getchar();
	while(isdigit(ch))	x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	x=f?-x:x;
}
const long long N=2e3+5;
long long a[N][N],n,m;
struct edge{
	long long ne,to,w;
}e[N*N*2];
long long h[N*N],cnt,ans,dis[N*N],vis[N*N];
inline void add(long long u,long long v,long long w)
{
	e[++cnt]={h[u],v,w};
	h[u]=cnt;
}
int  main()
{
	read(n);
	for(long long i=1;i<=n;i++)
		for(long long j=i,w;j<=n;j++)
			read(w),add(i-1,j,w),add(j,i-1,w);
	priority_queue<pair<int,int> > q;
	memset(dis,0x3f,sizeof(dis));
	q.push(make_pair(0,1)),dis[1]=0;
	while(!q.empty())
	{
		long long x=q.top().second;q.pop();
		if(vis[x])	continue;
		vis[x]=1,ans+=dis[x];
		for(long long i=h[x];i;i=e[i].ne)
		{
			long long y=e[i].to;
			if(dis[y]>e[i].w)	dis[y]=e[i].w,q.push(make_pair(-dis[y],y));
		}
	}
	printf("%lld\n",ans);
	return 0;
}

P1967 [NOIP2013 提高组] 货车运输(经典题)

题目背景

NOIP2013 提高组 D1T3

题目描述

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

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

输入格式

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

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

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

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

输出格式

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

样例 #1
样例输入 #1
4 3
1 2 4
2 3 3
3 1 1
3
1 3
1 4
1 3
样例输出 #1
3
-1
3
提示

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

对于 60 % 60\% 60% 的数据, 1 ≤ n < 1000 1 \le n < 1000 1n<1000 1 ≤ m < 5 × 1 0 4 1 \le m < 5\times 10^4 1m<5×104 1 ≤ q < 1000 1 \le q< 1000 1q<1000

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

思路

最优情况下一定是走最大生成树,所以相当于询问最大生成树上两点之间的最小边权,
我们用倍增维护即可。

倍增求 LCA
int lca(int x,int y) {
if (d[x] > d[y]) swap(x, y);
for (int i = t;i >= 0; i--)
if (d[f[y][i]] >= d[x]) y = f[y][i];
if (x == y) return x;
for (int i = t;i >= 0; i--)
if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
return f[x][0];
}
AC代码
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
bool f;
int n,m,q;
struct T{
	int st,ed,v;
}edge[N];
int pre[N];
bool inline cmp( T a,T b){
	return a.v>b.v;
}
inline int found(int x){
	if(pre[x]==x)return x;
	return pre[x]=found(pre[x]);
}
vector<T>G[N];
int F[N][20],num[N][20],lg[N],deep[N],root[N];
bool mark[N],vis[N];
void DFS(int x,int fa,int val,int rt){
    root[x]=rt;
    F[x][0]=fa;
    num[x][0]=val;
    deep[x]=deep[fa]+1;
    for(int i=1;(1<<i)<=deep[x];i++)F[x][i]=F[F[x][i-1]][i-1],num[x][i]=min(num[x][i-1],num[F[x][i-1]][i-1]);
        for(int i=0;i<G[x].size();i++){
            int t=G[x][i].ed,val=G[x][i].v;
            if(t==fa)continue;
                DFS(t,x,val,rt);
            }
    }
inline int LCA(int x,int y){
    if(deep[x]<deep[y])swap(x,y);
    int res=2e9+7;
    while(deep[x]>deep[y]){
        res=min(res,num[x][lg[deep[x]-deep[y]]-1]);
        x=F[x][lg[deep[x]-deep[y]]-1];
    }
    if(x==y)return res;
    for(int i=lg[deep[x]]-1;i>=0;i--){
        if(F[x][i]==F[y][i])continue;
        res=min(res,num[x][i]);
        res=min(res,num[y][i]);
        x=F[x][i],y=F[y][i];
}
    res=min(res,num[x][0]);
    res=min(res,num[y][0]);
    return res;
}
inline void work(){
     for(int i=1;i<=N-10;i++)lg[i]=lg[i-1]+((1<<lg[i-1])==i);
     for(int i=1;i<=n;i++)pre[i]=i;
     for(int i=1; i<=m; i++) {
        int x,y,z;
        scanf("%d %d %d",&x,&y,&z);
        edge[i]=T {x,y,z};
    }
    sort(edge+1,edge+1+m,cmp);
    for(int i=1;i<=m;i++){
        int fx=found(edge[i].st),fy=found(edge[i].ed);
        if(fx==fy)continue;
        mark[edge[i].st]=mark[edge[i].ed]=true;
        pre[fx]=fy;
        G[edge[i].st].push_back(T{0,edge[i].ed,edge[i].v});
        G[edge[i].ed].push_back(T{0,edge[i].st,edge[i].v});
    }
    for(int i=1;i<=n;i++)
        if(!root[i])
            DFS(i,0,0,i);    
    scanf("%d",&q);
    for(int i=1;i<=q;i++){
        int x,y;
        scanf("%d %d",&x,&y);
        if(root[x]!=root[y]){
                puts("-1");
                continue;
        }
    	printf("%d\n",LCA(x,y));
    }
}
int main(){
	scanf("%d %d",&n,&m);
    work();
    return 0;
}

P4180 [BJWC2010] 严格次小生成树

题目描述

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

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

输入格式

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

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

输出格式

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

样例 #1
样例输入 #1
5 6
1 2 1 
1 3 2 
2 4 3 
3 5 4 
3 4 3 
4 5 6
样例输出 #1
11
提示

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

对于 50 % 50\% 50% 的数据, N ≤ 2000 N\le 2000 N2000 M ≤ 3000 M\le 3000 M3000

对于 80 % 80\% 80% 的数据, N ≤ 5 × 1 0 4 N\le 5\times 10^4 N5×104 M ≤ 1 0 5 M\le 10^5 M105

对于 100 % 100\% 100% 的数据, N ≤ 1 0 5 N\le 10^5 N105 M ≤ 3 × 1 0 5 M\le 3\times10^5 M3×105,边权 ∈ [ 0 , 1 0 9 ] \in [0,10^9] [0,109],数据保证必定存在严格次小生成树。

思路

次小生成树一定是将某个非树边代替换上的一条边形成的。
那么一定代替最大值或严格次大值,倍增记录即可。

AC代码
#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);
const ll N = 100000 + 50,M = 300000 + 50, F = 30 + 5;
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;
}
struct Edge {
	ll to, ne, w;
} edge[M << 1];
ll head[N], cnt;
void add (ll u, ll v, ll w) {
	edge[++cnt].to = v;
	edge[cnt].w = w;
	edge[cnt].ne = head[u];
	head[u] = cnt;
}

ll f[N][F], g[N][F], h[N][F], dep[N];

inline void ckmax (ll &x, ll y) {
	x = (x > y ? x : y);
}

inline void dfs (ll u, ll fa, ll w) {
	dep[u] = dep[fa] + 1;
	f[u][0] = fa;
	g[u][0] = w;
	h[u][0] = -INF;
	for (ll 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 (ll i = head[u]; i; i = edge[i].ne ) {
		ll v = edge[i].to, w = edge[i].w;
		if (v == fa) continue;
		dfs (v, u, w);
	}
}

inline ll LCA (ll x, ll y) {
	if (dep[x] < dep[y]) swap (x, y);
	for (ll i = 20; i >= 0; i -- ) {
		if (dep[f[x][i]] >= dep[y]) x = f[x][i];
	}
	if (x == y) return x;
	for (ll i = 20; i >= 0; i -- ) {
		if (f[x][i] != f[y][i]) {
			x = f[x][i];
			y = f[y][i];
		}
	}
	return f[x][0];
}

ll n, m, fa[N], res, sum;

struct Node {
	ll u, v, w;
	bool operator < (const Node & rhs ) const  {
		return w < rhs.w;
	}
} a[M << 1];

inline ll find (ll x) {
	return x == fa[x] ? x : fa[x] = find (fa[x]);
}

inline void merge (ll x, ll y) {
	x = find (x); y = find (y);
	if (x == y) return;
	fa[x] = y;
}

bool vis[M];

inline void kruskal () {
	n = read (); m = read ();
	for (ll i = 1; i <= m; i ++ ) {
		a[i].u = read (); a[i].v = read (); a[i].w = read ();
	}
	sort (a + 1, a + m + 1);
	for (ll i = 1; i <= n; i ++ ) fa[i] = i;
	res = 0;
	for (ll 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;
			add (a[i].u, a[i].v, a[i].w);
			add (a[i].v, a[i].u, a[i].w);
		}
		if (res == n - 1) break;
	}
	res = 0;
	dfs (1, 0, 0);
}

inline ll qmax (ll u, ll v, ll maxx) {
	ll ans = -INF;
	for (ll 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 (ll &x, ll y) {
	x = (x < y ? x : y);
}

inline void calc () {
	ll ans = INF;
	for (ll i = 1; i <= m; i ++ ) {
		if (vis[i]) continue;
		ll lca = LCA (a[i].u, a[i].v);
		ll max_u = qmax (a[i].u, lca, a[i].w);
		ll 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);
}

int main() {
	kruskal ();
	calc ();
	return 0;
}

CF827D Best Edge Weight

题面翻译

给定一个点数为 n n n,边数为 m m m,权值不超过 1 0 9 10^9 109 的带权连通图,没有自环与重边。
现在要求对于每一条边求出,这条边的边权最大为多少时,它还能出现在所有可能的最小生成树上,如果对于任意边权都出现,则输出 − 1 -1 1
($ 2 \le n \le 2 \times 10^5, n - 1 \le m \le 2 \times 10^5$)

感谢 @WJiannan 提供的翻译

题目描述

You are given a connected weighted graph with $ n $ vertices and $ m $ edges. The graph doesn’t contain loops nor multiple edges. Consider some edge with id $ i $ . Let’s determine for this edge the maximum integer weight we can give to it so that it is contained in all minimum spanning trees of the graph if we don’t change the other weights.

You are to determine this maximum weight described above for each edge. You should calculate the answer for each edge independently, it means there can’t be two edges with changed weights at the same time.

输入格式

The first line contains two integers $ n $ and $ m $ ( $ 2<=n<=2·10^{5} $ , $ n-1<=m<=2·10^{5} $ ), where $ n $ and $ m $ are the number of vertices and the number of edges in the graph, respectively.

Each of the next $ m $ lines contains three integers $ u $ , $ v $ and $ c $ ( $ 1<=v,u<=n $ , $ v≠u $ , $ 1<=c<=10^{9} $ ) meaning that there is an edge between vertices $ u $ and $ v $ with weight $ c $ .

输出格式

Print the answer for each edge in the order the edges are given in the input. If an edge is contained in every minimum spanning tree with any weight, print -1 as the answer.

样例 #1
样例输入 #1
4 4
1 2 2
2 3 2
3 4 2
4 1 3
样例输出 #1
2 2 2 1
样例 #2
样例输入 #2
4 3
1 2 2
2 3 2
3 4 2
样例输出 #2
-1 -1 -1
思路

分别考虑树边和非树边
非树边:只需比环上最大的边小即可,倍增维护即可
树边:需要小于所有跨过这个树边的非树边中的最小值,倍增打 tag
我要炸了,这道题代码自己写吧

这是我的第十七篇文章,如有纰漏也请各位大佬指正
辛苦创作不易,还望看官点赞收藏打赏,后续还会更新新的内容。

  • 19
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值