搜索与图论洛谷习题
拓扑排序
P6145 [USACO20FEB]Timeline G
Bessie 在过去的 M 天内参加了 N 次挤奶。但她已经忘了她每次挤奶是在哪个时候了。
对于第 i 次挤奶,Bessie 记得它不早于第 Si 天进行。另外,她还有 C 条记忆,每条记忆形如一个三元组 (a,b,x),含义是第 b 次挤奶在第 a 次挤奶结束至少 x 天后进行。
现在请你帮 Bessie 算出在满足所有条件的前提下,每次挤奶的最早日期。
保证 Bessie 的记忆没有错误,这意味着一定存在一种合法的方案,使得:
①第 i 次挤奶不早于第 Si 天进行,且不晚于第 M 天进行;
②所有的记忆都得到满足;
输入格式
第一行三个整数 N,M,C。保证 1 ≤ N,C ≤105, 2 ≤ M ≤109 。
接下来一行包含 N 个整数 S1, S2 ,……, Sn,保证∀1≤ i ≤n,都满足 1≤ S ≤ M。
下面 C 行每行三个整数 a,b,x,描述一条记忆。保证 a ≠ b,且1≤ x ≤M。
输出格式
输出 N 行,每行一个整数,第 i 行的数表示第 i 次挤奶的最早日期。
输入输出样例
输入
4 10 3
1 2 3 4
1 2 5
2 4 2
3 4 4
输出
1
6
3
8
算法思路:
因为记忆信息顺序不定,可能会导致重复查询更新,所以应采用拓扑排序。
①先用a[N]数组存放每次挤奶的不早于的时间,再将三元组(a, b, x)存入邻接表中,并记录每个节点的入度。
②在拓扑排序中,将入度为0的节点入队列,入度为0的节点可作为起点,枚举队头的出边,更新出边对应节点的挤奶最早日期,并将出边的入度减一,若减为0,则入队列。
③输出更新好的最早日期。
这里更新最早日期需同时满足b ≥ a + w, i ≥ Si,所以取两者的最大值即可。
Code:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int n, m, c, idx;
int e[N], ne[N], h[N], w[N];
int d[N], q[N], a[N];
void add(int a, int b, int x)
{
e[idx] = b;
ne[idx] = h[a];
w[idx] = x;
h[a] = idx ++;
}
void topsort()
{
int hh = 0, tt = -1;
for(int i = 1; i <= n; i ++)
if(!d[i]) q[++ tt] = i;
while(hh <= tt)
{
int t = q[hh ++];
for(int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
a[j] = max(a[j], a[t] + w[i]);//根据记忆,更新a[N]数组
if(-- d[j] == 0) q[++ tt] = j;
}
}
}
int main()
{
cin >> n >> m >> c;
memset(h, -1, sizeof h);
for(int i = 1; i <= n; i ++) cin >> a[i];
while(c --)
{
int a, b, x;
cin >> a >> b >> x;
add(a, b, x);
d[b] ++;
}
topsort();
for(int i = 1; i <= n; i ++) cout << a[i] << endl;
return 0;
}
P1983 车站分级
算法思路:
如果车停靠了一个站点,所有等级大于等于这个站点的站点都要停靠,反过来也就是所有起始站和终点站中间没有停靠的站点的等级一定是小于停靠的站点。所以将起点到终点所有没停靠的站点与停靠的站点连边,然后进行一次拓扑排序就可以了。
Code:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 2e6 + 10, M = 1005;
int n, m, idx, ans;
int e[N], ne[N], h[M], a[M], d[M], w[M];//a停靠车站编号,d入度,w等级
bool st[M][M], vis[M];//st记录是否有连边,vis记录是否为停靠站
queue<int> q;
void add(int a, int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx ++;
}
void topsort()
{
for(int i = 1; i <= n; i ++)
if(!d[i])
{
q.push(i);
w[i] = 1;
}
while(!q.empty())
{
int t = q.front();
q.pop();
for(int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
w[j] = w[t] + 1;
ans = max(ans, w[j]);
if(-- d[j] == 0) q.push(j);
}
}
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
for(int i = 1; i <= m; i ++)
{
memset(a, 0, sizeof a);
memset(vis, false, sizeof vis);
int k;
cin >> k;
for(int j = 1; j <= k; j ++)
{
cin >> a[j];
vis[a[j]] = true;//停靠的站标记为true
}
for(int j = a[1]; j <= a[k]; j ++)
if((!vis[j]))//如果是不停靠的站
{
for(int t = 1; t <= k; t ++)
{
if(!st[j][a[t]])//该不停靠的站与停靠站a[t]没有连边
{
add(j, a[t]);
st[j][a[t]] = true;
d[a[t]] ++;//连边,使得a[t]入度加一
}
}
}
}
topsort();
cout << ans;
return 0;
}
最短路径
P5837 [USACO19DEC]Milk Pumping G
算法思路:
本题主要就是找出一条最短路径,使得min(f[i])/∑c[i]最大。管道的使用是双向的,所以存入的是无向的邻接表。然后枚举f[i]查找在此最小流量下,走符合条件最短路(即费用最少),对应的流量与费用之比。最终的答案即为最大的流量与费用之比。这里,找最短路时运用到了堆优化的Dijkstra算法。
Code:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef pair<int, int> PII;
const int N = 2e5 + 10;
int n, m, idx, ans;
int e[N], ne[N], h[N];
int c[N], f[N], cost[N];//c两接合点的管道费用, f接点管道的流量,cost[i]从1到i的费用
bool st[N];
void add(int a, int b, int x, int d)
{
e[idx] = b;
ne[idx] = h[a];
c[idx] = x;
f[idx] = d;
h[a] = idx ++;
}
int dijkstra(int limit)
{
memset(cost, 0x3f, sizeof cost);
memset(st, false, sizeof st);
cost[1] = 0;
priority_queue<PII, vector<PII>, greater<PII> > heap;
heap.push({0, 1});//第一维是费用,第二维是编号
while(heap.size())
{
PII t = heap.top();
heap.pop();
int money = t.first, ver = t.second;
if(st[ver]) continue;//冗余备份
st[ver] = true;
for(int i = h[ver]; i != -1; i = ne[i])
{
if(f[i] < limit) continue;//小于最小流量,不符合条件
int j = e[i];
if(cost[j] > money + c[i])
{
cost[j] = money + c[i];
if(!st[j])
heap.push({cost[j], j});
}
}
}
if(cost[n] == 0x3f3f3f3f) return -1;
return cost[n];
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
for(int i = 1; i <= m; i ++)
{
int a, b, x, d;
cin >> a >> b >> x >> d;
add(a, b, x, d), add(b, a, x, d);
}
for(int i = 0; i < idx; i ++)
if(dijkstra(f[i]) != -1)
ans = max(ans, f[i]*int(1e6)/ dijkstra(f[i]));
cout << ans << endl;
return 0;
}
P1576 最小花费
在n个人中,某些人的银行账号之间可以互相转账。这些人之间转账的手续费各不相同。给定这些人之间转账时需要从转账金额里扣除百分之几的手续费,请问A最少需要多少钱使得转账后B收到100元。
输入格式
第一行输入两个正整数n,m,分别表示总人数和可以互相转账的人的对数。
以下m行每行输入三个正整数x,y,z,表示标号为x的人和标号为y的人之间互相转账需要扣除z%的手续费 (z<100)。
最后一行输入两个正整数A,B。数据保证A与B之间可以直接或间接地转账。
输出格式
输出A使得B到账100元最少需要的总费用。精确到小数点后8位。
输入输出样例
输入
3 3
1 2 1
2 3 2
1 3 3
1 3
输出
103.07153164
说明/提示
1<=n<=2000,m<=100000
算法思路:
两人之间互相转账需要扣除z%的手续费即为两个人之间实际的汇率是 1-z%。利用Dijkstra算法找到最大的汇率乘积就可以计算出最少的总费用。
Code:
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
typedef pair<double, int> PII;
const int N = 2e6 + 10;
int n, m, idx;
int e[N], ne[N], h[N];
double w[N], dist[N] ;
bool st[N];
void add(int a, int b, double z)
{
e[idx] = b;
ne[idx] = h[a];
w[idx] = z;
h[a] = idx ++;
}
double Dijkstra(int A, int B)
{
memset(dist, 0x3f, sizeof dist);
dist[A] = 1.0;
priority_queue<PII, vector<PII>, greater<PII> > heap;
heap.push({1.0, A});
while(heap.size())
{
PII t = heap.top();
heap.pop();
double rate = 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(dist[j] < rate * w[i])
{
dist[j] = rate * w[i];
if(!st[j])
heap.push({dist[j], j});
}
}
}
if(dist[B] == 0x3f3f3f3f) return -1;
return dist[B];
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
while(m --)
{
int a, b;
float c;
cin >> a >> b >> z;
add(a, b, 1 - z/100);
}
int A, B;
cin >> A >> B;
printf("%.8lf", 100/Dijkstra(A, B));
return 0;
}
P1828 [USACO3.2]香甜的黄油 Sweet Butter
农夫John发现做出全威斯康辛州最甜的黄油的方法:糖。把糖放在一片牧场上,他知道N(1<=N<=500)只奶牛会过来舔它,这样就能做出能卖好价钱的超甜黄油。当然,他将付出额外的费用在奶牛上。
农夫John很狡猾。像以前的Pavlov,他知道他可以训练这些奶牛,让它们在听到铃声时去一个特定的牧场。他打算将糖放在那里然后下午发出铃声,以至他可以在晚上挤奶。
农夫John知道每只奶牛都在各自喜欢的牧场(一个牧场不一定只有一头牛)。给出各头牛在的牧场和牧场间的路线,找出使所有牛到达的路程和最短的牧场(他将把糖放在那)
输入格式
第一行: 三个数:奶牛数N,牧场数(2<=P<=800),牧场间道路数C(1<=C<=1450)
第二行到第N+1行: 1到N头奶牛所在的牧场号
第N+2行到第N+C+1行:
每行有三个数:相连的牧场A、B,两牧场间距离D(1<=D<=255),当然,连接是双向的
输出格式
一行 输出奶牛必须行走的最小的距离和
输入输出样例
输入
3 4 5
2
3
4
1 2 1
1 3 5
2 3 7
2 4 3
3 4 5
输出
8
算法思路:
本题给出了每个奶牛的初始位置,选择哪个牧场可使所有奶牛到该牧场总路程最小。利用逆向推导,就是枚举每个牧场,计算以该牧场为起点到每个奶牛的所在位置的最短路径,该过程就可以用SPFA算法来实现。
Code:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
const int N = 2e5 + 10;
int n, p, c, idx, ans = 0x3f3f3f3f;
int e[N], ne[N], h[N], w[N];
int v[N], dist[N];//v存放初始位置
bool st[N];
void add(int a, int b, int x)
{
e[idx] = b;
ne[idx] = h[a];
w[idx] = x;
h[a] = idx ++;
}
void spfa(int x)
{
memset(dist, 0x3f, sizeof dist);
memset(st, false, sizeof st);
dist[x] = 0;
queue<int> q;
q.push(x);
st[x] = true;
while(!q.empty())
{
int t = q.front();
q.pop();
st[t] = false;
for(int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if(dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
if(!st[j])
{
q.push(j);
st[j] = true;
}
}
}
}
}
int main()
{
cin >> n >> p >> c;
memset(h, -1, sizeof h);
for(int i = 1; i <= n; i ++) cin >> v[i];
while(c --)
{
int a, b, x;
cin >> a >> b >> x;
add(a, b, x), add(b, a, x);
}
for(int i = 1; i <= p; i ++) //枚举放糖果的牧场
{
spfa(i); //求解出从终点i到各点的最短路数组dist
int sum = 0;
for(int j = 1; j <= n; j ++)//循环累加每个奶牛的最短路
sum += dist[v[j]];
ans = min(ans, sum);
}
cout << ans << endl;
return 0;
}
P2136 拉近距离
Code:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 2e6 + 10;
int n, m, idx, dist[N], cnt[N];
int e[N], ne[N], h[N], w[N];
bool st[N];
void add(int a, int b, int c)
{
e[idx] = b;
ne[idx] = h[a];
w[idx] = c;
h[a] = idx ++;
}
int spfa()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
queue<int> q;
q.push(1);
st[1] = true;
while(q.size())
{
int t = q.front();
q.pop();
st[t] = false;
for(int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if(dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
cnt[j] = cnt[t] + 1;
if(cnt[j] >= n) return -1;
if(!st[j])
{
q.push(j);
st[j] = true;
}
}
}
}
return dist[n];
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
while(m --)
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, -c);
}
int ans = spfa();
if(ans == -1) cout << "Forever love" << endl;
else cout << ans << endl;
return 0;
}