这周代码源做的少,都在洛谷上刷图论
所以周报只能交这个了hh。。不过这周其实还练了一些差分,打算写在另一篇博客;
主要刷了dijkstra、prim和kruskal的一些题,因为好久没做最短路题了,所以刷的都是黄题绿题之类的,下个星期再把难度提一提
修复公路
题目背景
A 地区在地震过后,连接所有村庄的公路都造成了损坏而无法通车。政府派人修复这些公路。
题目描述
给出 A 地区的村庄数 N N N,和公路数 M M M,公路是双向的。并告诉你每条公路的连着哪两个村庄,并告诉你什么时候能修完这条公路。问最早什么时候任意两个村庄能够通车,即最早什么时候任意两条村庄都存在至少一条修复完成的道路(可以由多条公路连成一条道路)。
输入格式
第 1 1 1 行两个正整数 N , M N,M N,M。
下面 M M M 行,每行 3 3 3 个正整数 x , y , t x,y,t x,y,t,告诉你这条公路连着 x , y x,y x,y 两个村庄,在时间t时能修复完成这条公路。
输出格式
如果全部公路修复完毕仍然存在两个村庄无法通车,则输出 − 1 -1 −1,否则输出最早什么时候任意两个村庄能够通车。
样例 #1
样例输入 #1
4 4
1 2 6
1 3 4
1 4 5
4 2 3
样例输出 #1
5
提示
1 ≤ x , y ≤ N ≤ 1 0 3 1\leq x, y\leq N \le 10 ^ 3 1≤x,y≤N≤103, 1 ≤ M , t ≤ 1 0 5 1\leq M, t \le 10 ^ 5 1≤M,t≤105。
挺水的,用并查集来存已经联通的村庄,记得要先给每条边按照时间先后排个序,这样当并查集中的连通块足够大的时候能够及时的输出
#include <bits/stdc++.h>
using namespace std;
int N, M;
int p[1005];
bool flag;
struct Edge
{
int x, y;
int t;
}edge[100005];
bool cmp(Edge A, Edge B)
{
return A.t < B.t;
}
int find(int x)
{
if(x != p[x]) return find(p[x]);
else return x;
}
int main()
{
cin >> N >> M;
for(int i = 1; i <= M; i ++) {
cin >> edge[i].x;
cin >> edge[i].y;
cin >> edge[i].t;
}
sort(edge + 1, edge + M + 1, cmp); //按时间排序
for(int i = 1; i <= N; i ++) p[i] = i; //并查集初始化
int cnt = 1; //记录已经有多少个村庄联通
for(int i = 1; i <= M; i ++) {
int x = edge[i].x, y = edge[i].y, t = edge[i].t;
if(find(x) != find(y))
{
p[find(x)] = find(y);
cnt ++;
}
if(cnt == N){cout << t; flag = true; break;}
}
if(!flag) cout << -1;
return 0;
}
[USACO3.1]最短网络 Agri-Net
题目背景
Farmer John 被选为他们镇的镇长!他其中一个竞选承诺就是在镇上建立起互联网,并连接到所有的农场。当然,他需要你的帮助。
题目描述
FJ 已经给他的农场安排了一条高速的网络线路,他想把这条线路共享给其他农场。为了用最小的消费,他想铺设最短的光纤去连接所有的农场。
你将得到一份各农场之间连接费用的列表,你必须找出能连接所有农场并所用光纤最短的方案。每两个农场间的距离不会超过 1 0 5 10^5 105。
输入格式
第一行农场的个数 N N N( 3 ≤ N ≤ 100 3 \leq N \leq 100 3≤N≤100)。
接下来是一个 N × N N \times N N×N 的矩阵,表示每个农场之间的距离。理论上,他们是 N N N 行,每行由 N N N 个用空格分隔的数组成,实际上,由于每行 80 80 80 个字符的限制,因此,某些行会紧接着另一些行。当然,对角线将会是 0 0 0,因为不会有线路从第 i i i 个农场到它本身。
输出格式
只有一个输出,其中包含连接到每个农场的光纤的最小长度。
样例 #1
样例输入 #1
4
0 4 9 21
4 0 8 17
9 8 0 16
21 17 16 0
样例输出 #1
28
提示
题目翻译来自NOCOW。
USACO Training Section 3.1
也是要先按边权排序,优先使用更短的边,直到所有点都被联通再输出
#include <bits/stdc++.h>
using namespace std;
int N, cnt, ans;
int p[105];
struct Edge
{
int a, b;
int w;
}edge[100005];
bool cmp(Edge A, Edge B)
{
return A.w < B.w;
}
int find(int x)
{
if(x != p[x]) return find(p[x]);
else return x;
}
int main()
{
cin >> N;
for(int i = 1; i <= N; i ++) {
p[i] = i; //顺便把并查集初始化了
for(int j = 1; j <= N; j ++) {
int t;
cin >> t;
edge[++ cnt].a = i;
edge[cnt].b = j;
edge[cnt].w = t;
} //每条边会存两次不过问题不大
}
sort(edge + 1, edge + cnt + 1, cmp);
int sum = 1; //记录已连接的农场
for(int i = 1; i <= cnt; i ++) {
int a = edge[i].a, b = edge[i].b, w = edge[i].w;
if(find(a) != find(b))
{
p[find(a)] = find(b);
sum ++;
ans += w;
}
if(sum == N) break;
}
cout << ans;
return 0;
}
[SCOI2005]繁忙的都市
题目描述
城市 C 是一个非常繁忙的大都市,城市中的道路十分的拥挤,于是市长决定对其中的道路进行改造。城市 C 的道路是这样分布的:城市中有 n n n 个交叉路口,有些交叉路口之间有道路相连,两个交叉路口之间最多有一条道路相连接。这些道路是双向的,且把所有的交叉路口直接或间接的连接起来了。每条道路都有一个分值,分值越小表示这个道路越繁忙,越需要进行改造。但是市政府的资金有限,市长希望进行改造的道路越少越好,于是他提出下面的要求:
- 改造的那些道路能够把所有的交叉路口直接或间接的连通起来。
- 在满足要求 1 的情况下,改造的道路尽量少。
- 在满足要求 1、2 的情况下,改造的那些道路中分值最大的道路分值尽量小。
任务:作为市规划局的你,应当作出最佳的决策,选择哪些道路应当被修建。
输入格式
第一行有两个整数 n , m n,m n,m 表示城市有 n n n 个交叉路口, m m m 条道路。
接下来 m m m 行是对每条道路的描述, u , v , c u, v, c u,v,c 表示交叉路口 u u u 和 v v v 之间有道路相连,分值为 c c c。
输出格式
两个整数 s , m a x s, \mathit{max} s,max,表示你选出了几条道路,分值最大的那条道路的分值是多少。
样例 #1
样例输入 #1
4 5
1 2 3
1 4 5
2 4 7
2 3 6
3 4 8
样例输出 #1
3 6
提示
数据范围及约定
对于全部数据,满足 1 ≤ n ≤ 300 1\le n\le 300 1≤n≤300, 1 ≤ c ≤ 1 0 4 1\le c\le 10^4 1≤c≤104, 1 ≤ m ≤ 1 0 5 1 \le m \le 10^5 1≤m≤105。
一道最小生成树的裸题,比较无脑,kruskal
#include <bits/stdc++.h>
using namespace std;
//最小生成树裸题,无脑。。
int N, M;
int p[305];
struct Edge
{
int a, b;
int w;
}edge[100005];
bool cmp(Edge A, Edge B)
{
return A.w < B.w;
}
int find(int x)
{
if(x != p[x]) return find(p[x]);
else return x;
}
int main()
{
cin >> N >> M;
for(int i = 1; i <= N; i ++) p[i] = i;
for(int i = 1; i <= M; i ++) {
int u, v, c;
cin >> u >> v >> c;
edge[i].a = u;
edge[i].b = v;
edge[i].w = c;
}
sort(edge + 1, edge + M + 1, cmp);
int s = 0, Max = 0; //记录已连接的路口数量,和最大边
for(int i = 1; i <= M; i ++) {
int a = edge[i].a, b = edge[i].b, w = edge[i].w;
if(find(a) != find(b))
{
p[find(a)] = find(b);
s ++;
Max = max(Max, w);
}
if(s == N - 1) break;
}
cout << s << ' ' << Max;
return 0;
}
[USACO09OCT]Heat Wave G
题目描述
有一个 n n n 个点 m m m 条边的无向图,请求出从 s s s 到 t t t 的最短路长度。
输入格式
第一行四个正整数
n
,
m
,
s
,
t
n,m,s,t
n,m,s,t。
接下来
m
m
m 行,每行三个正整数
u
,
v
,
w
u,v,w
u,v,w,表示一条连接
u
,
v
u,v
u,v,长为
w
w
w 的边。
输出格式
输出一行一个整数,表示答案。
样例 #1
样例输入 #1
7 11 5 4
2 4 2
1 4 3
7 2 2
3 4 3
5 7 5
7 3 3
6 1 1
6 3 4
2 4 3
5 6 3
7 2 1
样例输出 #1
7
提示
【数据范围】
对于
100
%
100\%
100% 的数据,
1
≤
n
≤
2500
1\le n \le 2500
1≤n≤2500,
1
≤
m
≤
6200
1\le m \le 6200
1≤m≤6200,
1
≤
w
≤
1000
1\le w \le 1000
1≤w≤1000。
【样例说明】
5
→
6
→
1
→
4
5 \to 6 \to 1 \to 4
5→6→1→4 为最短路,长度为
3
+
1
+
3
=
7
3+1+3 = 7
3+1+3=7。
有生成树裸题,自然要来一道dijkstra裸题,这两道就当做模板来复习一下,其实堆优化的dijkstra有蛮多细节需要注意的,比如说h数组要初始化-1。。PII有时候会爆int。。这个真的忘不掉了
#include <bits/stdc++.h>
using namespace std;
const int N = 2505, M = 6205;
typedef pair<int, int> PII; //存一对序号和距离
priority_queue <PII, vector<PII>, greater<PII> > heap;
int h[N], e[2 * M], ne[2 * M], w[2 * M], idx;
int dist[N];
bool st[N];
//dijkstra标配打好先
int n, m, s, t;
void add(int a, int b, int c)
{
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++;
}
void dijkstra(int s) //以s为起点求最短路
{
memset(dist, 0x3f, sizeof dist);
dist[s] = 0;
heap.push({0, s});
while(heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.second, distance = t.first;
if(st[ver]) continue;
st[ver] = true;
for(int i = h[ver]; i != -1; i = ne[i]) {
int j = e[i];
if(dist[j] > distance + w[i])
{
dist[j] = distance + w[i];
heap.push({dist[j], j});
}
}
}
return;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
memset(h, -1, sizeof h);
cin >> n >> m >> s >> t;
for(int i = 1; i <= m; i ++) {
int u, v, c;
cin >> u >> v >> c;
add(u, v, c);
add(v, u, c);
} //存双向边
dijkstra(s);
cout << dist[t];
return 0;
}
公路修建
题目描述
某国有 n n n 个城市,它们互相之间没有公路相通,因此交通十分不便。为解决这一“行路难”的问题,政府决定修建公路。修建公路的任务由各城市共同完成。
修建工程分若干轮完成。在每一轮中,每个城市选择一个与它最近的城市,申请修建通往该城市的公路。政府负责审批这些申请以决定是否同意修建。
政府审批的规则如下:
- 如果两个或以上城市申请修建同一条公路,则让它们共同修建;
- 如果三个或以上的城市申请修建的公路成环。如下图,A 申请修建公路 AB,B 申请修建公路 BC,C 申请修建公路 CA。则政府将否决其中最短的一条公路的修建申请;
- 其他情况的申请一律同意。
一轮修建结束后,可能会有若干城市可以通过公路直接或间接相连。这些可以互相连通的城市即组成“城市联盟”。在下一轮修建中,每个“城市联盟”将被看作一个城市,发挥一个城市的作用。
当所有城市被组合成一个“城市联盟”时,修建工程也就完成了。
你的任务是根据城市的分布和前面讲到的规则,计算出将要修建的公路总长度。
输入格式
第一行一个整数 n n n,表示城市的数量。( n ≤ 5000 n \leq 5000 n≤5000)
以下 n n n 行,每行两个整数 x x x 和 y y y,表示一个城市的坐标。( − 1 0 6 ≤ x , y ≤ 1 0 6 -10^6 \leq x,y \leq 10^6 −106≤x,y≤106)
输出格式
一个实数,四舍五入保留两位小数,表示公路总长。(保证有惟一解)
样例 #1
样例输入 #1
4
0 0
1 2
-1 2
0 4
样例输出 #1
6.47
提示
修建的公路如图所示:
这是一道不那么水的生成树了,因为kruskal会被卡空间,对于边数非常多的稠密图,kruskal中把每条边都存下来的过程是非常费空间的,而prim只在更新距离的时候才会用到边长,可以现用现算,prim是每次把离树距离最近的点加到树上,再用新加的点去更新外界点到树的距离。我用的是堆优化版的prim,跟dijkstra很像,然后这题有个点特别有启示意义,就是double数组的memset不可以用0x3f,会变成非常小的数,double的无穷大要用0x7f
#include <bits/stdc++.h>
using namespace std;
/*仔细想想其实第二个条件是不可能成立的
所以其实就是求最小生成树,
不过不能kruskal。。
交了一发MLE了。。*/
/*
Prim算法优势: 稠密图, 尤其是完全图.
因为在Kruskal算法中,
必须事先求出所有边的长度才能对之排序.
对于一个有5000节点的完全图,
这样做空间开销是巨大的.
而Prim只在更新点到树的距离时需要用到边长,
因此对于这种给坐标的完全图, 可以现用现算
*/
//堆优化版的prim,跟dijkstra如出一辙啊
//不过不用存图了,爽。。。
/*
另一个坑
double 的无穷大要0x7f
int才用0x3f
卡我好久。。
*/
const int N = 5005;
typedef pair<double, int> PDI; //码的思维定式写了PII调不出来
priority_queue <PDI, vector<PDI>, greater<PDI> > heap;
int n;
int x[N], y[N]; //记录点坐标
double dist[N]; // 记录外界的点到树上的最短距离
bool st[N]; //判断点是否在树上
double ans;
double d(int a, int b) //求ab间距离
{
double t;
t = pow(pow(x[a] - x[b], 2) + pow(y[a] - y[b], 2), 0.5);
return t;
}
void prim(int s) //以s为起点求最小生成树
{
memset(dist, 0x7f, sizeof dist);
dist[s] = 0; //放到树上,距离为0
heap.push({0, s});
while(heap.size())
{
auto t = heap.top();
heap.pop();
double distance = t.first;
int ver = t.second;
if(st[ver]) continue;
st[ver] = true;
ans += distance; //到树上的这段距离需要修建公路
//接下来重新计算外界点到树上的距离
for(int i = 1; i <= n; i ++) {
if(st[i]) continue; //在树上的别算
double dist_new = d(ver, i);
if(dist_new < dist[i])
{
dist[i] = dist_new;
heap.push({dist[i], i});
}
}
}
return;
}
int main()
{
cin >> n;
for(int i = 1; i <= n; i ++) cin >> x[i] >> y[i];
prim(1);
cout << fixed << setprecision(2) << ans;
return 0;
}
通往奥格瑞玛的道路
题目背景
在艾泽拉斯大陆上有一位名叫歪嘴哦的神奇术士,他是部落的中坚力量。
有一天他醒来后发现自己居然到了联盟的主城暴风城。
在被众多联盟的士兵攻击后,他决定逃回自己的家乡奥格瑞玛。
题目描述
在艾泽拉斯,有 n n n 个城市。编号为 1 , 2 , 3 , … , n 1,2,3,\ldots,n 1,2,3,…,n。
城市之间有 m m m 条双向的公路,连接着两个城市,从某个城市到另一个城市,会遭到联盟的攻击,进而损失一定的血量。
每次经过一个城市,都会被收取一定的过路费(包括起点和终点)。路上并没有收费站。
假设 1 1 1 为暴风城, n n n 为奥格瑞玛,而他的血量最多为 b b b,出发时他的血量是满的。如果他的血量降低至负数,则他就无法到达奥格瑞玛。
歪嘴哦不希望花很多钱,他想知道,在可以到达奥格瑞玛的情况下,他所经过的所有城市中最多的一次收取的费用的最小值是多少。
输入格式
第一行 3 3 3 个正整数, n , m , b n,m,b n,m,b。分别表示有 n n n 个城市, m m m 条公路,歪嘴哦的血量为 b b b。
接下来有 n n n 行,每行 1 1 1 个正整数, f i f_i fi。表示经过城市 i i i,需要交费 f i f_i fi 元。
再接下来有 m m m 行,每行 3 3 3 个正整数, a i , b i , c i a_i,b_i,c_i ai,bi,ci( 1 ≤ a i , b i ≤ n 1\leq a_i,b_i\leq n 1≤ai,bi≤n)。表示城市 a i a_i ai 和城市 b i b_i bi 之间有一条公路,如果从城市 a i a_i ai 到城市 b i b_i bi,或者从城市 b i b_i bi 到城市 a i a_i ai,会损失 c i c_i ci 的血量。
输出格式
仅一个整数,表示歪嘴哦交费最多的一次的最小值。
如果他无法到达奥格瑞玛,输出 AFK
。
样例 #1
样例输入 #1
4 4 8
8
5
6
10
2 1 2
2 4 1
1 3 4
3 4 3
样例输出 #1
10
提示
对于 60 % 60\% 60% 的数据,满足 n ≤ 200 n\leq 200 n≤200, m ≤ 1 0 4 m\leq 10^4 m≤104, b ≤ 200 b\leq 200 b≤200;
对于 100 % 100\% 100% 的数据,满足 n ≤ 1 0 4 n\leq 10^4 n≤104, m ≤ 5 × 1 0 4 m\leq 5\times 10^4 m≤5×104, b ≤ 1 0 9 b\leq 10^9 b≤109;
对于 100 % 100\% 100% 的数据,满足 c i ≤ 1 0 9 c_i\leq 10^9 ci≤109, f i ≤ 1 0 9 f_i\leq 10^9 fi≤109,可能有两条边连接着相同的城市。
细节多到爆炸的一题,PII爆int我愣是找不出来,惯性思维唉,光是二分还不够,虽然二分已经够难想的了,,,还要把二分的初始边界尽量压缩,具体见代码吧
#include <bits/stdc++.h>
using namespace std;
/*
哎。时隔两天来debug
发现是pair里面又写成了PII
然后爆int了
我真的焯了
心态炸裂
那天de了巨久。。。
*/
/*
语文不好。。
要求的是
经过城市最多的一次
这次的各个费用中的最小值是多少
*/
/*
用的钱越多
越有可能在血量耗尽前到终点
所以具有单调性
对钱进行二分
每条边的收费都小于等于此值
并且
走到终点之后血量不会被扣光
check里用dijkstra跑一边检验可不可行
边权是耗血量
*/
const int N = 10005, M = 50005;
typedef pair<long long, int> PLI;
int h[N], e[2 * M], ne[2 * M], idx;
long long dist[N];
bool st[N];
long long c[2 * M], f[N]; //耗血量,耗钱量
int n, m;
long long b;
long long l, r, mid;
void add(int a, int b, long long w)
{
e[idx] = b, ne[idx] = h[a], c[idx] = w, h[a] = idx ++;
}
void dijkstra(int s, long long mid)
{
priority_queue <PLI, vector<PLI>, greater<PLI> > heap;
memset(dist, 0x7f, sizeof dist);
memset(st, 0, sizeof st);
//long long用memset好像有点问题?....
dist[s] = 0;
heap.push({0, s});
while(heap.size())
{
auto t = heap.top();
heap.pop();
long long distance = t.first;
int ver = t.second;
if(st[ver]) continue;
st[ver] = true;
for(int i = h[ver]; i != -1; i = ne[i]) {
int j = e[i];
if(f[j] > mid) continue;
if(dist[j] > distance + c[i])
{
dist[j] = distance + c[i];
heap.push({dist[j], j});
}
}
}
}
bool check(long long mid)
{
dijkstra(1, mid);
if(dist[n] > b) return false; //耗血过多
else return true;
}
int main()
{
memset(h, -1, sizeof h);
cin >> n >> m >> b;
for(int i = 1; i <= n; i ++) {
cin >> f[i];
r = max(r, f[i]);
}
l = max(f[1], f[n]);
for(int i = 1; i <= m; i ++) {
int u, v; long long w;
cin >> u >> v >> w;
add(u, v, w);
add(v, u, w);
}
//l, r的取值是有说法的
//l是max(f[1], f[n])
//r是max(f[1 ~ n])
long long Max = r;
while(l <= r)
{ //mid是每个点能接受的最大收费超过的点就不要了
mid = (l + r) >> 1;
if(check(mid)) r = mid - 1;
else l = mid + 1;
}
if(!check(Max)) cout << "AFK";
else cout << l;
return 0;
}
电车
题目描述
在一个神奇的小镇上有着一个特别的电车网络,它由一些路口和轨道组成,每个路口都连接着若干个轨道,每个轨道都通向一个路口(不排除有的观光轨道转一圈后返回路口的可能)。在每个路口,都有一个开关决定着出去的轨道,每个开关都有一个默认的状态,每辆电车行驶到路口之后,只能从开关所指向的轨道出去,如果电车司机想走另一个轨道,他就必须下车切换开关的状态。
为了行驶向目标地点,电车司机不得不经常下车来切换开关,于是,他们想请你写一个程序,计算一辆从路口 A A A 到路口 B B B 最少需要下车切换几次开关。
输入格式
第一行有 3 3 3 个整数 N , A , B N,A,B N,A,B( 2 ≤ N ≤ 100 , 1 ≤ A , B ≤ N 2 \leq N \leq 100, 1 \leq A,B \leq N 2≤N≤100,1≤A,B≤N),分别表示路口的数量,和电车的起点,终点。
接下来有 N N N 行,每行的开头有一个数字 K i K_i Ki( 0 ≤ K i ≤ N − 1 0 \leq K_i \leq N-1 0≤Ki≤N−1),表示这个路口与 K i K_i Ki 条轨道相连,接下来有 K i K_i Ki 个数字表示每条轨道所通向的路口,开关默认指向第一个数字表示的轨道。
输出格式
输出文件只有一个数字,表示从 A A A 到 B B B 所需的最少的切换开关次数,若无法从 A A A 前往 B B B,输出 − 1 -1 −1。
样例 #1
样例输入 #1
3 2 1
2 2 3
2 3 1
2 1 2
样例输出 #1
0
抽象题建图的典范,,只要懂得如何建图就很好过
#include <bits/stdc++.h>
using namespace std;
/*
不看题解不会系列
关键在建图。。
把每一个车站看成一个点
这个站相连的第一个车站建立一条边权为0的边
对于它所相连的其他车站
建立边权为1的边
太牛了。。。
然后随便用哪种最短路都行
*/
const int INF = 100000000;
const int N = 105, M = 10050;
int h[N], e[M], ne[M], w[M], idx;
int dist[N];
bool st[N];
int n, A, B;
typedef pair<int, int> PII;
void add(int a, int b, int c)
{
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++;
}
void dijkstra(int s)
{
priority_queue <PII, vector<PII>, greater<PII> > heap;
for(int i = 1; i <= n; i ++) dist[i] = INF;
dist[s] = 0;
heap.push({0, s});
while(heap.size())
{
auto t = heap.top();
heap.pop();
int distance = t.first, ver = t.second;
if(st[ver]) continue;
st[ver] = true;
for(int i = h[ver]; i != -1; i = ne[i]) {
int j = e[i];
if(dist[j] > distance + w[i])
{
dist[j] = distance + w[i];
heap.push({dist[j], j});
}
}
}
}
int main()
{
memset(h, -1, sizeof h);
cin >> n >> A >> B;
for(int i = 1; i <= n; i ++) {
int k, r;
cin >> k;
if(!k) continue;
cin >> r;
add(i, r, 0);
for(int j = 2; j <= k; j ++) {
cin >> r;
add(i, r, 1);
}
}
dijkstra(A);
if(dist[B] == INF) cout << -1;
else cout << dist[B];
return 0;
}
emm这里是8题,虽然要求一周写10题,但由于另外几题写的并不是这个专题的,所以不放在这篇博客里了,我打算下篇博客再一起整理出来,反正每周任务肯定会完成地。。。。。。