第四题一般为比较基础的图论,不会太难,但也不会让你做得太舒服,关键是找到模型然后稍微变通下(这里的变通还挺考验对图论中一些基本模型的理解的)。
简化题目的意思,寻找模型,图论有许多的算法,很多时候并不是不会写,而是没有简化出模型,不知用何算法。
同时注意计算结果的范围是否会爆int。
复杂度计算(有些题其实可以无脑爆搜),一般1s的运算量为10^8.
which means:
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
2014
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
问题描述 目前在一个很大的平面房间里有 n 个无线路由器,每个无线路由器都固定在某个点上。任何两个无线路由器只要距离不超过 r 就能互相建立网络连接。 除此以外,另有 m 个可以摆放无线路由器的位置。你可以在这些位置中选择至多 k 个增设新的路由器。 你的目标是使得第 1 个路由器和第 2 个路由器之间的网络连接经过尽量少的中转路由器。请问在最优方案下中转路由器的最少个数是多少? 输入格式 第一行包含四个正整数 n,m,k,r。(2 ≤ n ≤ 100,1 ≤ k ≤ m ≤ 100, 1 ≤ r ≤ 108)。 接下来 n 行,每行包含两个整数 xi 和 yi,表示一个已经放置好的无线 路由器在 (xi, yi) 点处。输入数据保证第 1 和第 2 个路由器在仅有这 n 个路由器的情况下已经可以互相连接(经过一系列的中转路由器)。 接下来 m 行,每行包含两个整数 xi 和 yi,表示 (xi, yi) 点处可以增设 一个路由器。 输入中所有的坐标的绝对值不超过 108,保证输入中的坐标各不相同。 输出格式 输出只有一个数,即在指定的位置中增设 k 个路由器后,从第 1 个路 由器到第 2 个路由器最少经过的中转路由器的个数。 样例输入 5 3 1 3 0 0 5 5 0 3 0 5 3 5 3 3 4 4 3 0 样例输出 2
比较早年的第四题,所以题目还是比较简单的,之后会发现许多都是版子。这道题我们先简化一下题目的意思:(n+m)个点,每步步长限制为r,求从1点到2点的最小步数~。(吐槽一下这里题目中把n个点和m个点分开了,故弄玄虚,考试的话要清醒呀)。简化题目意思后能较为清晰地发现这就是一道搜索题,一下子蹦出两种思路bfs,dfs。
BFS
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
struct Node { int id, step; Node(int a, int b): id(a), step(b) {} }; inline bool can_go(const ll& x1,const ll&y1, const ll& x2, const ll& y2) { if((x1 - x2) > maxr || (x1 - x2) < -maxr) return false; if((y1 - y2) > maxr || (y1 - y2) < -maxr) return false; if(((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2)) > maxr*maxr) return false; return true; } int bfs(int s) { queue<Node> q; while(q.size()) q.pop(); q.push(Node(s, 0)); vis[s] = true; while(q.size()) { Node u = q.front(); q.pop(); int id = u.id; if(id == 2) return u.step-1; //减去终点才是中间的点 for(int i=1; i<=n+m; ++i) { if(vis[i]) continue; if(!can_go(stuff[id].x, stuff[id].y, stuff[i].x, stuff[i].y)) continue; q.push(Node(i, u.step+1)); vis[i] = true; } } return 0; }
BFS是层次的遍历,所以同层次的点不会相互访问,若一条路中有同层次的点相互访问那么该路必然不是最短路,所以vis数组可以看作是一层一层地标记访问点,这也是为何vis是在某点广度拓展时立刻标记拓展到的点(这样之后拓展到的点才不会相互访问)。由于这里没有建图,每次拓展时判断是否可达,相当于在遍历一个邻接矩阵,所以复杂度为O(n^2),由于n+m只有200,随意爆搜啦~
DFS
DFS与DP基本是一个东西,都是状态的转移,只是大多数情况下dfs更容易理解,但dp一般更快。关键找到状态即递推的子结构,且无后向性,(后向性可自行百度一下)有后向性无法使用dp,使用dfs的话很多重复遍历复杂度很高。在这题中图其实是无向的,两点间的影响是相互的存在后向性,而且没有理想的子结构可以递推,所以这题无法dp,强行dfs(最朴素的回溯dfs)就会运行超时~
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
2014
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
问题描述 栋栋最近开了一家餐饮连锁店,提供外卖服务。随着连锁店越来越多,怎么合理的给客户送餐成为了一个急需解决的问题。 栋栋的连锁店所在的区域可以看成是一个n×n的方格图(如下图所示),方格的格点上的位置上可能包含栋栋的分店(绿色标注)或者客户(蓝色标注),有一些格点是不能经过的(红色标注)。 方格图中的线表示可以行走的道路,相邻两个格点的距离为1。栋栋要送餐必须走可以行走的道路,而且不能经过红色标注的点。 送餐的主要成本体现在路上所花的时间,每一份餐每走一个单位的距离需要花费1块钱。每个客户的需求都可以由栋栋的任意分店配送,每个分店没有配送总量的限制。 现在你得到了栋栋的客户的需求,请问在最优的送餐方式下,送这些餐需要花费多大的成本。 输入格式 输入的第一行包含四个整数n, m, k, d,分别表示方格图的大小、栋栋的分店数量、客户的数量,以及不能经过的点的数量。 接下来m行,每行两个整数xi, yi,表示栋栋的一个分店在方格图中的横坐标和纵坐标。 接下来k行,每行三个整数xi, yi, ci,分别表示每个客户在方格图中的横坐标、纵坐标和订餐的量。(注意,可能有多个客户在方格图中的同一个位置) 接下来d行,每行两个整数,分别表示每个不能经过的点的横坐标和纵坐标。 输出格式 输出一个整数,表示最优送餐方式下所需要花费的成本。 样例输入 10 2 3 3 1 1 8 8 1 5 1 2 3 3 6 7 2 1 2 2 2 6 8 样例输出 29 评测用例规模与约定 前30%的评测用例满足:1<=n <=20。 前60%的评测用例满足:1<=n<=100。 所有评测用例都满足:1<=n<=1000,1<=m, k, d<=n^2。可能有多个客户在同一个格点上。每个客户的订餐量不超过1000,每个客户所需要的餐都能被送到。
简化题目意思:一张n*n的方格图,n个源点,m个目标点,寻找从任意多个源点出发到达所有目标点的最短路的和(至于费用,乘一下餐数就好)。
这种典型的迷宫类型的搜索很容易就联想到BFS,但是我们使用的bfs一般都是单源点,终点可多。所以我一开始的想法就是以客户为起点bfs找到最近的餐馆但这样需要调用m次bfs,m<=n^2,所以总的复杂度可以达到O(n^4),对于n<=1000的数据显然是不行滴。怎么办!哈,这就是考察对bfs的理解成都啦。刚才说过bfs是层次的遍历,再具体点就是以源点为中心向外扩张的层次遍历,所以搜索到的点是到源点距离最短的。仔细想想,其实这个源点其实也是一个层次,之前也说过vis是一层一层标记的,所以源点可以看成是最基本的层次,所以bfs搜索到的点其实是到这个基本层的最短距离。所以对于这道多源点的题目,bfs的基本层可以具体化为源点集!那么之后搜索到的点,就是到这个源点集距离最小的。具体的操作就是在队列初始化时,将所有餐馆的位置就入列,这样就完成了基本层的初始化聊~
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
#include <cstdio> #include <iostream> #include <algorithm> #include <queue> #define ll long long using namespace std; const int N = 1000+4, ban = -3; int g[N][N]; bool vis[N][N]; int n, m, k, d; struct Node{ int x, y, step; Node(int a, int b, int c): x(a), y(b), step(c) {} }; int dir[][2] = { {1,0}, {-1,0}, {0,1}, {0,-1} }; queue<Node> q; inline void __ini__() { while(q.size()) q.pop(); scanf("%d %d %d %d", &n, &m, &k, &d); int x, y; for(int i=1; i<=m; ++i) { // 餐馆 scanf("%d %d", &x, &y); q.push(Node(x, y, 0)); vis[x][y] = true; } for(int i=1; i<=k; ++i) { // 记录送餐数 int cnt; scanf("%d %d %d", &x, &y, &cnt); g[x][y] += cnt; } for(int i=1; i<=d; ++i) { // 标记不能到达 scanf("%d %d", &x, &y); g[x][y] = ban; } return; } // @return 到达所有近客户花费成本 ll bfs() { ll ans = 0; while(q.size()) { Node u = q.front(); q.pop(); for(int i=0; i<4; ++i) { int nx = u.x + dir[i][0], ny = u.y + dir[i][1]; if(nx < 1 || nx > n || ny < 1 || ny > n) continue; if(vis[nx][ny]) continue; if(g[nx][ny] == ban) continue; if(g[nx][ny] > 0) ans += (u.step+1) * g[nx][ny], k -= 1; // 记录花费 vis[nx][ny] = true; if(k == 0) break; // 所有客户都已经送完 q.push(Node(nx, ny, u.step+1)); } } return ans; } int main() { __ini__(); printf("%lld\n", bfs()); return 0; }
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
2014
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
问题描述 雷雷承包了很多片麦田,为了灌溉这些麦田,雷雷在第一个麦田挖了一口很深的水井,所有的麦田都从这口井来引水灌溉。 为了灌溉,雷雷需要建立一些水渠,以连接水井和麦田,雷雷也可以利用部分麦田作为“中转站”,利用水渠连接不同的麦田,这样只要一片麦田能被灌溉,则与其连接的麦田也能被灌溉。 现在雷雷知道哪些麦田之间可以建设水渠和建设每个水渠所需要的费用(注意不是所有麦田之间都可以建立水渠)。请问灌溉所有麦田最少需要多少费用来修建水渠。 输入格式 输入的第一行包含两个正整数n, m,分别表示麦田的片数和雷雷可以建立的水渠的数量。麦田使用1, 2, 3, ……依次标号。 接下来m行,每行包含三个整数ai, bi, ci,表示第ai片麦田与第bi片麦田之间可以建立一条水渠,所需要的费用为ci。 输出格式 输出一行,包含一个整数,表示灌溉所有麦田所需要的最小费用。 样例输入 4 4 1 2 1 2 3 4 2 4 2 3 4 3 样例输出 6 样例说明 建立以下三条水渠:麦田1与麦田2、麦田2与麦田4、麦田4与麦田3。 评测用例规模与约定 前20%的评测用例满足:n≤5。 前40%的评测用例满足:n≤20。 前60%的评测用例满足:n≤100。 所有评测用例都满足:1≤n≤1000,1≤m≤100,000,1≤ci≤10,000。
简化题目意思:n个点,给m条边让你选择连起来使得花费最小,且各点之间连通。
题目意思简化完之后这tm不就是最小生成树吗,还是一个纯板子~最小生成树的最终目标就是全局最优。所以这道题我就不费口水聊,emm还是提提把。最小生成树最常用的就是两个算法Kruskal,Prim,两者的思想都是贪心。
Kruskal的贪心是对于边,将所有边有大到小排序,由最小的开始选择,知道图连通后完成算法,这里的连通性通过并查集可以很高效地判断。总的复杂度是(ElogE),图较为稠密的时候推荐使用。
Prim的贪心是对于点,随便取一点作为最初的树(反正所有点都要集合成一棵树),接着遍历所有点找到到树距离最小的点,加入树中,之后由于树的大小改变,更新剩余点到树的距离。朴素的复杂度(N^2),但是堆优化(使用优先队列)后复杂度极大降低为(NlogN)--不管是在稀疏图还是稠密图都很强啊!时间上快了,空间开销自然比较大,但在本题中的数据量还是ok的。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
#include <cstdio> #include <cstring> #include <algorithm> #define ll long long using namespace std; const int N = 1000+4, M = 100000+4; struct Edge { int u, v, w; bool operator < (const Edge& b) const { return this->w < b.w; } }e[M]; int n, m; int fa[N]; inline void __ini__() { scanf("%d %d", &n, &m); for(int i=1; i<=n; ++i) fa[i] = i; for (int i = 0; i < m; ++i){ scanf("%d %d %d", &e[i].u, &e[i].v, &e[i].w); } return; } inline int find_fa(int q) { while(q != fa[q]) { fa[q] = fa[fa[q]]; q = fa[q]; } return q; } // 复杂度 m void kruskal() { sort(e, e+m); ll ans = 0; int cnt = 0; for(int i=0; i<m; ++i) { if(find_fa(e[i].u) != find_fa(e[i].v)) { fa[find_fa(e[i].u)] = find_fa(e[i].v); cnt += 1; ans += e[i].w; if(cnt == n-1) break; } } printf("%lld\n", ans); } int main() { __ini__(); kruskal(); return 0; }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
#include <iostream> #include <cstdio> #include <cstring> #include <queue> #define ll long long using namespace std; const int N = 1000+4, M = 100000+4; struct Edge { int to, w, nxt; }e[M*2]; struct Node { int u, w; Node(int a, int b): u(a), w(b) {} bool operator < (const Node& b) const { return this->w > b.w; } }; int head[N], cnte, dis[N]; // 到树的距离 bool vis[N]; int n, m; inline void add_edge(int from, int to, int w) { ++cnte; e[cnte].to = to, e[cnte].w = w, e[cnte].nxt = head[from]; head[from] = cnte; return; } inline void __ini__() { scanf("%d %d", &n, &m); int a, b, c; for(int i=0; i<m; ++i) { scanf("%d %d %d", &a, &b, &c); add_edge(a, b, c); add_edge(b, a, c); //双向边 } return; } // 堆优化复杂度 nlogn void Prim() { ll ans = 0; memset(dis, 0x3f, sizeof(dis)); priority_queue<Node> pq; while(pq.size()) pq.pop(); pq.push(Node(1, 0)); dis[1] = 0; while(pq.size()) { Node now = pq.top(); pq.pop(); int u = now.u; if(vis[u]) continue; vis[u] = true; // 加入树中 ans += now.w; // 费用累计 for(int i=head[u]; i; i=e[i].nxt) { int v = e[i].to; if(vis[v]) continue; if(dis[v] > e[i].w) { dis[v] = e[i].w; pq.push(Node(v, dis[v])); } } } printf("%lld\n", ans); } int main() { __ini__(); Prim(); return 0; } Prim堆优化版本
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
问题描述 给定一个公司的网络,由n台交换机和m台终端电脑组成,交换机与交换机、交换机与电脑之间使用网络连接。交换机按层级设置,编号为1的交换机为根交换机,层级为1。其他的交换机都连接到一台比自己上一层的交换机上,其层级为对应交换机的层级加1。所有的终端电脑都直接连接到交换机上。 当信息在电脑、交换机之间传递时,每一步只能通过自己传递到自己所连接的另一台电脑或交换机。请问,电脑与电脑之间传递消息、或者电脑与交换机之间传递消息、或者交换机与交换机之间传递消息最多需要多少步。 输入格式 输入的第一行包含两个整数n, m,分别表示交换机的台数和终端电脑的台数。 第二行包含n - 1个整数,分别表示第2、3、……、n台交换机所连接的比自己上一层的交换机的编号。第i台交换机所连接的上一层的交换机编号一定比自己的编号小。 第三行包含m个整数,分别表示第1、2、……、m台终端电脑所连接的交换机的编号。 输出格式 输出一个整数,表示消息传递最多需要的步数。 样例输入 4 2 1 1 3 2 1 样例输出 4
简化题目意思:给你一(n+m)个节点的棵树,求其直径。
意思很明确聊,如何求树的直径呢--树根已经给出啦,从树根开始深搜到最远的叶子节点,接着再从该叶子节点开始深搜到离其最远的叶子节点,两次bfs,100分我来啦 (^@^)~
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
#include <iostream> #include <cstdio> #include <cstring> #include <queue> #define ll long long using namespace std; const int N = 20000+10; struct Edge { int to, w, nxt; }e[N*2]; struct Node { int u, step; Node(int a, int b): u(a), step(b) {} }; int head[N], cnte; bool vis[N]; int n, m; inline void add_edge(int from, int to) { ++cnte; e[cnte].to = to, e[cnte].nxt = head[from]; head[from] = cnte; return; } int get_id(int q) { // 电脑的编号特殊处理 return q + N/2; } void __ini__() { scanf("%d %d", &n, &m); for(int i=2; i<=n; ++i) { int a; scanf("%d", &a); add_edge(i, a); add_edge(a, i); } for(int i=1; i<=m; ++i) { int a; scanf("%d", &a); add_edge(get_id(i), a); add_edge(a, get_id(i)); } return; } /* @return leaf 从根节点bfs找到最远的叶子 */ int bfs1() { memset(vis, false, sizeof(vis)); int leaf = 0, step = 0; queue<Node> q; while(q.size()) q.pop(); q.push(Node(1, 0)); vis[1] = true; while(q.size()) { Node now = q.front(); q.pop(); int u = now.u; for(int i=head[u]; i; i=e[i].nxt) { int v = e[i].to; if(vis[v]) continue; vis[v] = true; if(step < now.step+1) { // 判断是否找到最远的叶子 step = now.step + 1; leaf = v; } q.push(Node(v, now.step+1)); } } return leaf; } void bfs2() { int s = bfs1(); // 上一次找到的叶子节点为起点 memset(vis, false, sizeof(vis)); int step = 0; queue<Node> q; while(q.size()) q.pop(); q.push(Node(s, 0)); vis[s] = true; while(q.size()) { Node now = q.front(); q.pop(); int u = now.u; for(int i=head[u]; i; i=e[i].nxt) { int v = e[i].to; if(vis[v]) continue; vis[v] = true; step = (step<(now.step+1)?(now.step+1): step); q.push(Node(v, now.step+1)); } } printf("%d\n", step); } int main() { __ini__(); bfs2(); return 0; }
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
(中间有几次的第四题就是板子题我就懒得再写聊~)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
问题描述 给定一段文字,已知单词a1, a2, …, an出现的频率分别t1, t2, …, tn。可以用01串给这些单词编码,即将每个单词与一个01串对应,使得任何一个单词的编码(对应的01串)不是另一个单词编码的前缀,这种编码称为前缀码。 使用前缀码编码一段文字是指将这段文字中的每个单词依次对应到其编码。一段文字经过前缀编码后的长度为: L=a1的编码长度×t1+a2的编码长度×t2+…+ an的编码长度×tn。 定义一个前缀编码为字典序编码,指对于1 ≤ i < n,ai的编码(对应的01串)的字典序在ai+1编码之前,即a1, a2, …, an的编码是按字典序升序排列的。 例如,文字E A E C D E B C C E C B D B E中, 5个单词A、B、C、D、E出现的频率分别为1, 3, 4, 2, 5,则一种可行的编码方案是A:000, B:001, C:01, D:10, E:11,对应的编码后的01串为1100011011011001010111010011000111,对应的长度L为3×1+3×3+2×4+2×2+2×5=34。 在这个例子中,如果使用哈夫曼(Huffman)编码,对应的编码方案是A:000, B:01, C:10, D:001, E:11,虽然最终文字编码后的总长度只有33,但是这个编码不满足字典序编码的性质,比如C的编码的字典序不在D的编码之前。 在这个例子中,有些人可能会想的另一个字典序编码是A:000, B:001, C:010, D:011, E:1,编码后的文字长度为35。 请找出一个字典序编码,使得文字经过编码后的长度L最小。在输出时,你只需要输出最小的长度L,而不需要输出具体的方案。在上面的例子中,最小的长度L为34。 输入格式 输入的第一行包含一个整数n,表示单词的数量。 第二行包含n个整数,用空格分隔,分别表示a1, a2, …, an出现的频率,即t1, t2, …, tn。请注意a1, a2, …, an具体是什么单词并不影响本题的解,所以没有输入a1, a2, …, an。 输出格式 输出一个整数,表示文字经过编码后的长度L的最小值。
这一次的第四题就稍微与之前相比有了更多的变化不再是单纯的图了。在了解哈夫曼编码的前提下,我们知道存储编码的二叉树在查询的时候左0右1,所以右边的节点的编码值必然比左边节点的编码值字典序大,所以只要一开始将单词字典序排好后不能再改变相互的次序来编码,就能保证编码符合题目的字典序要求。这样我们在合并点的时候就只能选取相邻的点合并,在点合并了后则是点集的合并,所以一开始就可一看成是区间的合并。
简化题目的意思:给定一列带有权值的区间(最初始区间只有一个点),每次只能合并相邻的区间,每次合并的消耗为两区间的权值和,求将所有区间归一的最小消耗。
这就是典型的石子合并问题,区间dp。状态的递推为f(i,j) = min(f(i, k) + f(k+1, j)+ w(i,j)),f(i,j)表示 i 到 j 这个闭区间的最小消耗,w(i,j)表示 i 到 j 闭区间所有点的权值和,即在合并时的消耗值。最初始的状态为f(i,i)== 0,所以子结构是可以确定的,递推可以进行。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
#include <cstdio> #include <iostream> #include <cstring> using namespace std; const int N = 1000+4; int f[N][N]; int a[N], n; inline int _min(const int& a, const int& b) { return a<b? a:b; } inline void dp() { for(int len=2; len<=n; ++len) { for(int i=1; i<=n; ++i) { int j = i + len-1; if(j > n) break; for(int k=i; k<=j; ++k) { f[i][j] = _min(f[i][j], f[i][k] + f[k+1][j] + a[j]-a[i-1]); } } }return; } int main() { scanf("%d", &n); memset(f, 0x3f, sizeof(f)); for(int i=1; i<=n; ++i) { scanf("%d", a+i); f[i][i] = 0; a[i] += a[i-1]; } dp(); printf("%d\n", f[1][n]); return 0; }
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
问题描述
G国国王来中国参观后,被中国的高速铁路深深的震撼,决定为自己的国家也建设一个高速铁路系统。
建设高速铁路投入非常大,为了节约建设成本,G国国王决定不新建铁路,而是将已有的铁路改造成高速铁路。现在,请你为G国国王提供一个方案,将现有的一部分铁路改造成高速铁路,使得任何两个城市间都可以通过高速铁路到达,而且从所有城市乘坐高速铁路到首都的最短路程和原来一样长。请你告诉G国国王在这些条件下最少要改造多长的铁路。
输入格式
输入的第一行包含两个整数n, m,分别表示G国城市的数量和城市间铁路的数量。所有的城市由1到n编号,首都为1号。
接下来m行,每行三个整数a, b, c,表示城市a和城市b之间有一条长度为c的双向铁路。这条铁路不会经过a和b以外的城市。
输出格式
输出一行,表示在满足条件的情况下最少要改造的铁路长度。
这道题就有点意思啦,要在理解算法的基础上融汇贯通~
简化题目意思:一张已经连通的双向图,在单元最短路不变的前提下,选择消耗最少的边集使得在边集中图连通。
单源最短路最稳的算法当然就是堆优化之后的dijkstra啦,消耗最少使图连通--典型的最小生成树。注意这里的主次关系:是在最短路不变的情况下考虑最小生成树,即在局部最优(单源最短路)的前提下实现全局最优(最小生成树)。dijkstra的主体思想是贪心地选点--这与Prim的思想是一样的,只不过最短路是到源点的距离,最小生成树是到目前树的距离。所以我们对dijkstra改动一下,每次dis[v] > dis[u] + w则进行松弛操作(u为当前已经确定最短路的点),说明要满足最短路这条w为权值的边要选,所以次优先的prim生成树则要取w为v点到树的距离。嗯嗯,因为最短路可能会有多条,所以在遇到多条最短路可选时就进行最小生成树的判断,即当dis[v] == dis[u] + w时, t[v]= MIN(t[v], w),t[v]是v点到树的距离 。最中结果求下prim过程中每次加入的点到树的距离就行了。(如果你不了解dijkstra和prim建议先去学习学习。。)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
#include <cstdio> #include <iostream> #include <cstring> #include <queue> #define Inf 0x3f3f3f3f using namespace std; const int N = 10000+4, M = 100000+4; struct Node { int u, dis; Node(int a, int b): u(a), dis(b) {} bool operator < (const Node& b) const { return this->dis > b.dis; } }; struct Edge { int u, to, w; int nxt; }e[M*2]; // 双向边 int dis[N], t[N]; int hd[N], cnte; int n, m; bool vis[N]; inline void add_edge(const int& from, const int& to, const int& w) { ++cnte; e[cnte].to = to, e[cnte].w = w, e[cnte].nxt = hd[from]; hd[from] = cnte; return; } void dijkstra() { memset(dis, Inf, sizeof(dis)), memset(t, Inf, sizeof(t)); priority_queue<Node> pq; while(pq.size()) pq.pop(); pq.push(Node(1, 0)); dis[1] = 0, t[1] = 0; while(pq.size()) { Node now = pq.top(); pq.pop(); if(vis[now.u]) continue; vis[now.u] = true; for(int i=hd[now.u]; i; i=e[i].nxt) { int v = e[i].to; if(dis[v] > dis[now.u] + e[i].w) { dis[v] = dis[now.u] + e[i].w; t[v] = e[i].w; pq.push(Node(v, dis[v])); }else if(dis[v] == dis[now.u] + e[i].w) { if(t[v] > e[i].w) t[v] = e[i].w; } } }return; } int main() { scanf("%d %d", &n, &m); for(int i=0; i<m; ++i) { int a, b, c; scanf("%d %d %d", &a, &b, &c); add_edge(a, b, c), add_edge(b, a, c); } dijkstra(); int ans = 0; for(int i=1; i<=n; ++i) { ans += t[i]; }printf("%d\n", ans); return 0; }
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =