[toc]

单源最短路的建图方式

1129. 热浪

德克萨斯纯朴的民众们这个夏天正在遭受巨大的热浪!!!
他们的德克萨斯长角牛吃起来不错,可是它们并不是很擅长生产富含奶油的乳制品。
农夫John此时身先士卒地承担起向德克萨斯运送大量的营养冰凉的牛奶的重任,以减轻德克萨斯人忍受酷暑的痛苦。
John已经研究过可以把牛奶从威斯康星运送到德克萨斯州的路线。
这些路线包括起始点和终点一共有 T 个城镇,为了方便标号为 1 到 T。
除了起点和终点外的每个城镇都由 双向道路 连向至少两个其它的城镇。
每条道路有一个通过费用(包括油费,过路费等等)。
给定一个地图,包含 C 条直接连接 2 个城镇的道路。
每条道路由道路的起点 Rs,终点 Re 和花费 Ci 组成。
求从起始的城镇 Ts 到终点的城镇 Te 最小的总费用。
输入格式
第一行: 4 个由空格隔开的整数: T,C,Ts,Te;
第 2 到第 C+1 行: 第 i+1 行描述第 i 条道路,包含 3 个由空格隔开的整数: Rs,Re,Ci。
输出格式
一个单独的整数表示从 Ts 到 Te 的最小总费用。
数据保证至少存在一条道路。
输入样例:
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
输出样例:
7

  1. #include <cstring>
  2. #include <iostream>
  3. #include <algorithm>
  4. using namespace std;
  5. const int N = 2510;
  6. int n, m;
  7. int ts,td;
  8. int g[N][N];
  9. int dist[N];
  10. bool st[N];
  11. int dijkstra()
  12. {
  13. memset(dist, 0x3f, sizeof dist);
  14. dist[ts] = 0;
  15. for (int i = 0; i < n - 1; i ++ ) // 这里是n-1次
  16. {
  17. int t = -1;
  18. for (int j = 1; j <= n; j ++ )
  19. if (!st[j] && (t == -1 || dist[t] > dist[j]))
  20. t = j;
  21. for (int j = 1; j <= n; j ++ )
  22. dist[j] = min(dist[j], dist[t] + g[t][j]);
  23. st[t] = true;
  24. }
  25. if (dist[n] == 0x3f3f3f3f) return -1;
  26. return dist[td];
  27. }
  28. int main()
  29. {
  30. scanf("%d%d", &n, &m);
  31. scanf("%d%d", &ts, &td);
  32. memset(g, 0x3f, sizeof g);
  33. while (m -- )
  34. {
  35. int a, b, c;
  36. scanf("%d%d%d", &a, &b, &c);
  37. g[a][b] = c; // 双向通道
  38. g[b][a] = c;
  39. }
  40. printf("%d\n", dijkstra());
  41. return 0;
  42. }

1128. 信使

战争时期,前线有 n 个哨所,每个哨所可能会与其他若干个哨所之间有通信联系。
信使负责在哨所之间传递信息,当然,这是要花费一定时间的(以天为单位)。
指挥部设在第一个哨所。
当指挥部下达一个命令后,指挥部就派出若干个信使向与指挥部相连的哨所送信。
当一个哨所接到信后,这个哨所内的信使们也以同样的方式向其他哨所送信。信在一个哨所内停留的时间可以忽略不计。
直至所有 n 个哨所全部接到命令后,送信才算成功。
因为准备充足,每个哨所内都安排了足够的信使(如果一个哨所与其他 k 个哨所有通信联系的话,这个哨所内至少会配备 k 个信使)。
现在总指挥请你编一个程序,计算出完成整个送信过程最短需要多少时间。
输入格式
第 1 行有两个整数 n 和 m,中间用 1 个空格隔开,分别表示有 n 个哨所和 m 条通信线路。
第 2 至 m+1 行:每行三个整数 i、j、k,中间用 1 个空格隔开,表示第 i 个和第 j 个哨所之间存在 双向 通信线路,且这条线路要花费 k 天。
输出格式
一个整数,表示完成整个送信过程的最短时间。
如果不是所有的哨所都能收到信,就输出-1。
输入样例:
4 4
1 2 4
2 3 7
2 4 1
3 4 6
输出样例:
11

做一遍dij,然后求最长路径,如果最长路径是一开始设置的正无穷,说明不能到达,否则最长路径就是答案

  1. #include <cstring>
  2. #include <iostream>
  3. #include <algorithm>
  4. using namespace std;
  5. const int N = 2510;
  6. int n, m;
  7. int g[N][N];
  8. int dist[N];
  9. bool st[N];
  10. int dijkstra()
  11. {
  12. memset(dist, 0x3f, sizeof dist);
  13. dist[1] = 0;
  14. for (int i = 0; i < n - 1; i ++ ) // 这里是n-1次
  15. {
  16. int t = -1;
  17. for (int j = 1; j <= n; j ++ )
  18. if (!st[j] && (t == -1 || dist[t] > dist[j]))
  19. t = j;
  20. for (int j = 1; j <= n; j ++ )
  21. dist[j] = min(dist[j], dist[t] + g[t][j]);
  22. st[t] = true;
  23. }
  24. }
  25. int main()
  26. {
  27. scanf("%d%d", &n, &m);
  28. memset(g, 0x3f, sizeof g);
  29. while (m -- )
  30. {
  31. int a, b, c;
  32. scanf("%d%d%d", &a, &b, &c);
  33. g[a][b] = c; // 双向通道
  34. g[b][a] = c;
  35. }
  36. dijkstra();
  37. int res=0;
  38. for (int i = 1; i <= n; i ++ ){
  39. res = max(res,dist[i]);
  40. }
  41. if(res==0x3f3f3f3f){
  42. cout << -1 <<endl;
  43. }else{
  44. cout << res;
  45. }
  46. return 0;
  47. }

1127. 香甜的黄油

农夫John发现了做出全威斯康辛州最甜的黄油的方法:糖。
把糖放在一片牧场上,他知道 N 只奶牛会过来舔它,这样就能做出能卖好价钱的超甜黄油。
当然,他将付出额外的费用在奶牛上。
农夫John很狡猾,就像以前的巴甫洛夫,他知道他可以训练这些奶牛,让它们在听到铃声时去一个特定的牧场。
他打算将糖放在那里然后下午发出铃声,以至他可以在晚上挤奶。
农夫John知道每只奶牛都在各自喜欢的牧场(一个牧场不一定只有一头牛)。
给出各头牛在的牧场和牧场间的路线,找出使所有牛到达的路程和最短的牧场(他将把糖放在那)。
数据保证至少存在一个牧场和所有牛所在的牧场连通。
输入格式
第一行: 三个数:奶牛数 N,牧场数 P,牧场间道路数 C。
第二行第 N+1 行: 1 到 N 头奶牛所在的牧场号。
第 N+2 行到第 N+C+1 行:每行有三个数:相连的牧场A、B,两牧场间距 D,当然,连接是双向的。
输出格式
共一行,输出奶牛必须行走的最小的距离和。
输入样例:
3 4 5
2
3
4
1 2 1
1 3 5
2 3 7
2 4 3
3 4 5
输出样例:
8

以每个点为起点,求最短路

  1. #include <iostream>
  2. #include <cstring>
  3. #include <algorithm>
  4. #include <vector>
  5. #include <queue>
  6. #define x first
  7. #define y second
  8. using namespace std;
  9. typedef pair<int, int> PII;
  10. const int N = 1e5+10,INF = 0X3f3f3f3f;
  11. vector<PII>h[N];
  12. int n,m,p;
  13. int dist[N],id[N];
  14. bool st[N];
  15. int spfa(int u){
  16. memset(dist, 0x3f, sizeof dist);
  17. dist[u] = 0;
  18. queue<int>q;
  19. q.push(u);
  20. while(!q.empty()){
  21. int a = q.front();
  22. st[a] = false;
  23. q.pop();
  24. for (int i = 0; i < h[a].size(); i ++ ){
  25. int b = h[a][i].x;
  26. int c = h[a][i].y;
  27. int t = dist[a]+c;
  28. if(t<dist[b]){
  29. dist[b] = t;
  30. if(!st[b]){
  31. q.push(b);
  32. st[b] = true;
  33. }
  34. }
  35. }
  36. }
  37. int res=0;
  38. for (int i = 0; i < n; i ++ ){
  39. int j = id[i];
  40. if(dist[j]==INF){ // 说明这头牛到不了,返回正无穷
  41. return INF;
  42. }
  43. res+=dist[j];
  44. }
  45. return res;
  46. }
  47. int main()
  48. {
  49. cin >> n >> p >> m;
  50. for (int i = 0; i < n; i ++ ){
  51. cin >> id[i];
  52. }
  53. while (m -- ){
  54. int a,b,c;
  55. cin >> a >> b >> c;
  56. h[a].push_back({b,c});
  57. h[b].push_back({a,c});
  58. }
  59. int res=INF;
  60. for (int i = 1; i <= p; i ++ ){
  61. res = min(res,spfa(i));
  62. }
  63. cout << res;
  64. }

1126. 最小花费(乘积最大值)

在 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
image.png

  1. #include <cstring>
  2. #include <iostream>
  3. #include <algorithm>
  4. using namespace std;
  5. const int N = 2010;
  6. int n, m;
  7. double g[N][N];
  8. double dist[N];
  9. bool st[N];
  10. double dijkstra(int start,int end)
  11. {
  12. memset(dist, 0, sizeof dist); // 乘积最大值初始化为0
  13. dist[start] = 1; //初始化为1
  14. for (int i = 0; i < n - 1; i ++ ) // 这里是n-1次
  15. {
  16. int t = -1;
  17. for (int j = 1; j <= n; j ++ )
  18. if (!st[j] && (t == -1 || dist[j] > dist[t]))
  19. t = j;
  20. for (int j = 1; j <= n; j ++ )
  21. dist[j] = max(dist[j], dist[t] * g[t][j]); // 这里求最大
  22. st[t] = true;
  23. }
  24. return dist[end];
  25. }
  26. int main()
  27. {
  28. scanf("%d%d", &n, &m);
  29. memset(g, 0, sizeof g); // 因为是求乘积最大值,所以初始化为0相当于正无穷
  30. while (m -- )
  31. {
  32. int a, b, c;
  33. scanf("%d%d%d", &a, &b, &c);
  34. g[a][b] = (100.0-c)/100;
  35. g[b][a] = (100.0-c)/100;
  36. }
  37. int A,B;
  38. cin >> A >> B;
  39. double res = 100/dijkstra(A,B);
  40. printf("%.8lf",res);
  41. return 0;
  42. }

920. 最优乘车

H 城是一个旅游胜地,每年都有成千上万的人前来观光。
为方便游客,巴士公司在各个旅游景点及宾馆,饭店等地都设置了巴士站并开通了一些单程巴士线路。
每条单程巴士线路从某个巴士站出发,依次途经若干个巴士站,最终到达终点巴士站。
一名旅客最近到 H 城旅游,他很想去 S 公园游玩,但如果从他所在的饭店没有一路巴士可以直接到达 S 公园,则他可能要先乘某一路巴士坐几站,再下来换乘同一站台的另一路巴士,这样换乘几次后到达 S 公园。
现在用整数 1,2,…N 给 H 城的所有的巴士站编号,约定这名旅客所在饭店的巴士站编号为 1,S 公园巴士站的编号为 N。
写一个程序,帮助这名旅客寻找一个最优乘车方案,使他在从饭店乘车到 S 公园的过程中换乘的次数最少。
输入格式
第一行有两个数字 M 和 N,表示开通了 M 条单程巴士线路,总共有 N 个车站。
从第二行到第 M+1 行依次给出了第 1 条到第 M 条巴士线路的信息,其中第 i+1 行给出的是第 i 条巴士线路的信息,从左至右按运行顺序依次给出了该线路上的所有站号,相邻两个站号之间用一个空格隔开。
输出格式
共一行,如果无法乘巴士从饭店到达 S 公园,则输出 NO,否则输出最少换乘次数,换乘次数为 0 表示不需换车即可到达。
输入样例:
3 7
6 7
4 7 3 6
2 1 3 5
输出样例:
2

  1. /*
  2. 将图经一号点的车加入队列
  3. 只要队列不空,取出车来,更新线路上的其他所有点的最短距离
  4. 对于其线路上的每一个点再遍历经过的车,如果没被用过就加入队列中
  5. */
  6. #include <iostream>
  7. #include <cstring>
  8. #include <algorithm>
  9. #include <vector>
  10. #include <queue>
  11. #include <sstream>
  12. #define x first
  13. #define y second
  14. const int N = 510,M=110,INF = 0x3f3f3f3f;
  15. using namespace std;
  16. typedef pair<int, int> PII;
  17. vector<int>line[M]; // i号车的线路
  18. vector<int>pid[N]; // 经过i号点的车有哪些
  19. int cid[N][N]; // i号车j号点对应线路里面的哪个下标
  20. int dist[N];
  21. bool st[N][N];
  22. int m,n;
  23. struct Node{
  24. int x; // 车
  25. int y; // 第几次换乘
  26. int z; // 在这条线路的几号点(这里指下标)换乘
  27. };
  28. void bfs(int start){
  29. memset(dist, 0x3f, sizeof dist);
  30. queue<Node>q;
  31. for(auto c:pid[start]){ // 包含起点的车入队列
  32. q.push({c,-1,cid[c][start]}); // x表示车,y第几次换乘
  33. st[c][start] = true;
  34. }
  35. while(!q.empty()){
  36. auto t = q.front();
  37. q.pop();
  38. int x = t.x; // x表示车,y第几次换乘
  39. int y = t.y;
  40. int z = t.z; // 从x车的这条线路上的第几个点开始
  41. // 用车更新沿线上的点的最短距离
  42. for (int i = z; i < line[x].size(); i ++ ){
  43. int point = line[x][i];
  44. // 看看在这个点能换乘哪些车
  45. for(auto c:pid[point]){
  46. if(!st[c][point]){
  47. q.push({c,y+1,cid[c][point]});
  48. st[c][point] = true;// 访问过这辆车
  49. }
  50. }
  51. //cout << point <<" ";
  52. // 更新线路上点的最短距离
  53. dist[point] = min(dist[point],y+1);
  54. if(point==n){ // 已经更新最短了
  55. return ;
  56. }
  57. }
  58. //cout << endl;
  59. }
  60. }
  61. int main()
  62. {
  63. cin >> m >> n;
  64. getchar();
  65. for (int i = 1; i <= m; i ++ ){
  66. string str;
  67. getline(cin,str);
  68. stringstream ssin(str);
  69. int x;
  70. int cnt=0;
  71. while(ssin>>x){
  72. line[i].push_back(x);
  73. pid[x].push_back(i);
  74. cid[i][x] = cnt;
  75. cnt++;
  76. }
  77. }
  78. bfs(1);
  79. if(dist[n]==INF){
  80. puts("NO");
  81. }else{
  82. cout << dist[n];
  83. }
  84. }

image.png

  1. #include <iostream>
  2. #include <cstring>
  3. #include <algorithm>
  4. #include <vector>
  5. #include <queue>
  6. #include <sstream>
  7. #define x first
  8. #define y second
  9. const int N = 510,M=110,INF = 0x3f3f3f3f;
  10. using namespace std;
  11. int dist[N];
  12. bool g[N][N];
  13. int stop[N];
  14. bool st[N];
  15. int m,n;
  16. void bfs(int start){
  17. queue<int>q;
  18. q.push(start);
  19. st[start] = true;
  20. memset(dist,0x3f,sizeof dist);
  21. dist[start] = 0;
  22. while(!q.empty()){
  23. auto t = q.front();
  24. q.pop();
  25. for (int j = 1; j <= n; j ++ ){
  26. if(g[t][j]&&!st[j]){
  27. st[j] = true;
  28. dist[j] = dist[t]+1;
  29. q.push(j);
  30. }
  31. }
  32. }
  33. }
  34. int main()
  35. {
  36. cin >> m >> n;
  37. getchar();
  38. for (int i = 1; i <= m; i ++ ){
  39. string str;
  40. getline(cin,str);
  41. stringstream ssin(str);
  42. int x;
  43. int cnt=0;
  44. while(ssin>>x){
  45. stop[cnt] =x;
  46. cnt++;
  47. }
  48. for (int i = 0; i < cnt; i ++ ){
  49. for (int j = i+1; j < cnt; j ++ ){
  50. g[stop[i]][stop[j]]=true;
  51. }
  52. }
  53. }
  54. bfs(1);
  55. if(dist[n]==INF){
  56. puts("NO");
  57. }else{
  58. cout <<max(dist[n]-1,0);
  59. }
  60. }

903. 昂贵的聘礼

年轻的探险家来到了一个印第安部落里。
在那里他和酋长的女儿相爱了,于是便向酋长去求亲。
酋长要他用 10000 个金币作为聘礼才答应把女儿嫁给他。
探险家拿不出这么多金币,便请求酋长降低要求。
酋长说:”嗯,如果你能够替我弄到大祭司的皮袄,我可以只要 8000 金币。如果你能够弄来他的水晶球,那么只要 5000 金币就行了。”
探险家就跑到大祭司那里,向他要求皮袄或水晶球,大祭司要他用金币来换,或者替他弄来其他的东西,他可以降低价格。
探险家于是又跑到其他地方,其他人也提出了类似的要求,或者直接用金币换,或者找到其他东西就可以降低价格。
不过探险家没必要用多样东西去换一样东西,因为不会得到更低的价格。
探险家现在很需要你的帮忙,让他用最少的金币娶到自己的心上人。
另外他要告诉你的是,在这个部落里,等级观念十分森严。
地位差距超过一定限制的两个人之间不会进行任何形式的直接接触,包括交易。
他是一个外来人,所以可以不受这些限制。
但是如果他和某个地位较低的人进行了交易,地位较高的的人不会再和他交易,他们认为这样等于是间接接触,反过来也一样。
因此你需要在考虑所有的情况以后给他提供一个最好的方案。
为了方便起见,我们把所有的物品从 1 开始进行编号,酋长的允诺也看作一个物品,并且编号总是 1。
每个物品都有对应的价格 P,主人的地位等级 L,以及一系列的替代品 Ti 和该替代品所对应的”优惠” Vi。
如果两人地位等级差距超过了 M,就不能”间接交易”。
你必须根据这些数据来计算出探险家最少需要多少金币才能娶到酋长的女儿。
输入格式
输入第一行是两个整数 M,N,依次表示地位等级差距限制和物品的总数。
接下来按照编号从小到大依次给出了 N 个物品的描述。
每个物品的描述开头是三个非负整数 P、L、X,依次表示该物品的价格、主人的地位等级和替代品总数。
接下来 X 行每行包括两个整数 T 和 V,分别表示替代品的编号和”优惠价格”。
输出格式
输出最少需要的金币数。
输入格式

1 4
10000 3 2
2 8000
3 5000
1000 2 1
4 200
3000 2 1
4 200
50 2 0

输出格式
5250
image.png

  1. #include <cstring>
  2. #include <iostream>
  3. #include <algorithm>
  4. using namespace std;
  5. const int N = 110,INF = 0x3f3f3f3f;
  6. int n, m;
  7. int g[N][N];
  8. int dist[N],level[N];
  9. bool st[N];
  10. int dijkstra(int down,int up)
  11. {
  12. memset(dist, 0x3f, sizeof dist);
  13. memset(st, 0, sizeof st); // 一定要在这里memset
  14. dist[0] = 0;
  15. for (int i = 0; i < n ; i ++ ) //
  16. {
  17. int t = -1;
  18. for (int j = 0; j <= n; j ++ )
  19. if (!st[j] && (t == -1 || dist[t] > dist[j]))
  20. t = j;
  21. for (int j = 1; j <= n; j ++ ){
  22. if(level[j]>=down&&level[j]<=up){
  23. dist[j] = min(dist[j], dist[t] + g[t][j]);
  24. }
  25. }
  26. st[t] = true;
  27. }
  28. return dist[1];
  29. }
  30. int main()
  31. {
  32. scanf("%d%d", &m, &n);
  33. memset(g, 0x3f, sizeof g);
  34. for (int i = 1; i <= n; i ++ ){
  35. int price,cnt;
  36. cin >> price >> level[i] >> cnt;
  37. // 建立虚拟源点0号点
  38. g[0][i] = min(g[0][i],price);
  39. for (int j = 0; j < cnt; j ++ ){
  40. int id,value;
  41. cin >> id >> value;
  42. g[id][i] = min(g[id][i],value);
  43. }
  44. }
  45. int res=INF;
  46. for (int i = level[1]-m; i <= level[1]; i ++ ){
  47. res = min( dijkstra(i,i+m), res);
  48. }
  49. cout << res;
  50. return 0;
  51. }

单源最短路的综合应用

1135. 新年好

重庆城里有 nn 个车站,mm 条 双向 公路连接其中的某些车站。
每两个车站最多用一条公路连接,从任何一个车站出发都可以经过一条或者多条公路到达其他车站,但不同的路径需要花费的时间可能不同。
在一条路径上花费的时间等于路径上所有公路需要的时间之和。
佳佳的家在车站 11,他有五个亲戚,分别住在车站 a,b,c,d,ea,b,c,d,e。
过年了,他需要从自己的家出发,拜访每个亲戚(顺序任意),给他们送去节日的祝福。
怎样走,才需要最少的时间?
输入格式
第一行:包含两个整数 n,mn,m,分别表示车站数目和公路数目。
第二行:包含五个整数 a,b,c,d,ea,b,c,d,e,分别表示五个亲戚所在车站编号。
以下 mm 行,每行三个整数 x,y,tx,y,t,表示公路连接的两个车站编号和时间。
输出格式
输出仅一行,包含一个整数 TT,表示最少的总时间。
输入样例:
6 6 2 3 4 5 6 1 2 8 2 3 3 3 4 4 4 5 5 5 6 2 1 6 7
输出样例:
21
image.png

  1. #include <iostream>
  2. #include <cstring>
  3. #include <algorithm>
  4. #include <vector>
  5. #include <queue>
  6. #define x first
  7. #define y second
  8. using namespace std;
  9. typedef pair<int, int> PII;
  10. const int N = 5e5+10,INF=0x3f3f3f3f;
  11. vector<PII>h[N];
  12. int n,m;
  13. int dist[6][N],source[6];
  14. bool st[N];
  15. int ans=INF;
  16. int dij(int start,int dist[]){
  17. memset(dist, 0x3f, N * 4);
  18. memset(st, 0, sizeof st);
  19. dist[start] = 0;
  20. priority_queue<PII,vector<PII>,greater<PII>>heap;
  21. heap.push({0,start});
  22. while(!heap.empty()){
  23. auto t=heap.top();
  24. heap.pop();
  25. int ver = t.y;
  26. int d1 = t.x;
  27. if(st[ver]){
  28. continue;
  29. }
  30. // 只能在这里标记访问
  31. st[ver] = true;
  32. for (int i = 0; i < h[ver].size(); i ++ ){
  33. int son = h[ver][i].x;
  34. int d2 = h[ver][i].y;
  35. if(st[son]){
  36. continue;
  37. }
  38. if(d1 + d2 < dist[son]){
  39. dist[son] = d1+d2;
  40. heap.push({d1+d2,son});
  41. }
  42. }
  43. }
  44. }
  45. // 全排列枚举每一种方案
  46. void dfs(int index,int last,int sum){
  47. if(index>=5){
  48. ans = min(sum,ans);
  49. }
  50. for (int i = 1; i <= 5; i ++ ){
  51. if(!st[i]){
  52. st[i] = true;
  53. dfs(index+1,i,sum+dist[last][source[i]]);
  54. st[i] = false;
  55. }
  56. }
  57. }
  58. int main()
  59. {
  60. scanf("%d%d", &n, &m);
  61. source[0]=1;
  62. for (int i = 1; i <=5; i ++ ){
  63. cin >> source[i];
  64. }
  65. while (m -- )
  66. {
  67. int a, b, c;
  68. scanf("%d%d%d", &a, &b, &c);
  69. h[a].push_back({b,c});
  70. h[b].push_back({a,c});
  71. }
  72. for (int i = 0; i < 6; i ++ ){
  73. dij(source[i],dist[i]);
  74. }
  75. memset(st, 0, sizeof st);
  76. dfs(0,0,0);
  77. cout << ans;
  78. return 0;
  79. }

340. 通信线路(双端队列求0,1最短路,二分)

在郊区有 N 座通信基站,P 条 双向 电缆,第 i 条电缆连接基站 Ai 和 Bi。
特别地,1 号基站是通信公司的总站,N 号基站位于一座农场中。
现在,农场主希望对通信线路进行升级,其中升级第 i 条电缆需要花费 Li。
电话公司正在举行优惠活动。
农产主可以指定一条从 1 号基站到 N 号基站的路径,并指定路径上不超过 K 条电缆,由电话公司免费提供升级服务。
农场主只需要支付在该路径上剩余的电缆中,升级价格最贵的那条电缆的花费即可。
求至少用多少钱可以完成升级。
输入格式
第 1 行:三个整数 N,P,K。
第 2..P+1 行:第 i+1 行包含三个整数 Ai,Bi,Li。
输出格式
包含一个整数表示最少花费。
若 1 号基站与 N 号基站之间不存在路径,则输出 −1。
输入样例:
5 7 1
1 2 5
3 1 4
2 4 8
3 2 3
5 2 9
3 4 7
4 5 6
输出样例:
4

  1. #include <iostream>
  2. #include <cstring>
  3. #include <algorithm>
  4. #include <vector>
  5. #include <deque>
  6. #define x first
  7. #define y second
  8. using namespace std;
  9. typedef pair<int, int> PII;
  10. const int N = 1010;
  11. int n,m,k;
  12. vector<PII>h[N];
  13. bool st[N];
  14. int dist[N];
  15. // 双端队列求0-1最短路
  16. bool check(int mid){
  17. deque<PII>q;
  18. memset(st, 0, sizeof st);
  19. memset(dist, 0x3f, sizeof dist);
  20. dist[1]=0;
  21. q.push_back({1,0});
  22. while(!q.empty()){
  23. auto t = q.front();
  24. q.pop_front();
  25. int x = t.x;
  26. int d = t.y;
  27. if(st[x]){
  28. continue;
  29. }
  30. st[x] = true;
  31. for (int i = 0; i < h[x].size(); i ++ ){
  32. int j = h[x][i].x;
  33. int w = h[x][i].y>mid;
  34. if(d+w<dist[j]){
  35. dist[j] = d+w;
  36. if(w>0){
  37. q.push_back({j,dist[j]});
  38. }else{
  39. q.push_front({j,dist[j]});
  40. }
  41. }
  42. }
  43. }
  44. return dist[n]<=k;
  45. }
  46. int main()
  47. {
  48. cin >> n >> m >> k;
  49. while (m -- ){
  50. int a,b,c;
  51. cin >> a >> b>> c;
  52. h[a].push_back({b,c});
  53. h[b].push_back({a,c});
  54. }
  55. int l=0,r=1e6+1;
  56. while(l<r){
  57. int mid = l+r>>1;
  58. if(check(mid)){
  59. r = mid;
  60. }else{
  61. l = mid+1;
  62. }
  63. }
  64. if(r==1e6+1){
  65. puts("-1");
  66. }else{
  67. cout << r<<endl;
  68. }
  69. }
  70. /*
  71. 二分原理
  72. mid 是当除去k条边后最大值是mid的情况下
  73. 看看能不能走到终点
  74. 那么就将大于mid的边设置为1,小于mid的边设置为0,
  75. 看看从1号点到n号点的权值为多少,如果大于k了说明不行,
  76. 如果小于k了说明可以
  77. */

342. 道路与航线(拓扑排序+dij,负权边+正权边)

农夫约翰正在一个新的销售区域对他的牛奶销售方案进行调查。
他想把牛奶送到 T 个城镇,编号为 1∼T。
这些城镇之间通过 R 条道路 (编号为 1 到 R) 和 P 条航线 (编号为 1 到 P) 连接。
每条道路 i 或者航线 i 连接城镇 Ai 到 Bi,花费为 Ci。
对于道路,0≤Ci≤10,000;然而航线的花费很神奇,花费 Ci 可能是负数(−10,000≤Ci≤10,000)。
道路是双向的,可以从 Ai 到 Bi,也可以从 Bi 到 Ai,花费都是 Ci。
然而航线与之不同,只可以从 Ai 到 Bi。
事实上,由于最近恐怖主义太嚣张,为了社会和谐,出台了一些政策:保证如果有一条航线可以从 Ai 到 Bi,那么保证不可能通过一些道路和航线从 Bi 回到 Ai。
由于约翰的奶牛世界公认十分给力,他需要运送奶牛到每一个城镇。
他想找到从发送中心城镇 S 把奶牛送到每个城镇的最便宜的方案。
输入格式
第一行包含四个整数 T,R,P,S。
接下来 R 行,每行包含三个整数(表示一个道路)Ai,Bi,Ci。
接下来 P 行,每行包含三个整数(表示一条航线)Ai,Bi,Ci。
输出格式
第 1..T 行:第 i 行输出从 S 到达城镇 i 的最小花费,如果不存在,则输出 NO PATH。
输入样例:
6 3 3 4
1 2 5
3 4 5
5 6 10
3 5 -100
4 6 -100
1 3 -10
输出样例:
NO PATH
NO PATH
5
0
-95
-100

image.png
image.png

  1. #include <iostream>
  2. #include <cstring>
  3. #include <algorithm>
  4. #include <vector>
  5. #define x first
  6. #define y second
  7. #include <queue>
  8. using namespace std;
  9. typedef pair<int, int> PII;
  10. const int N = 25010,INF = 0x3f3f3f3f;
  11. int n,mr,mp,S;
  12. vector<PII>h[N];
  13. int bid[N],cnt; // bid[i],i号点在哪个小分块当中,cnt总共几个小分块
  14. vector<int>block[N]; // 每个分块中的点
  15. bool st[N];
  16. int din[N]; // 每个分块的入度
  17. int dist[N];
  18. queue<int>q;
  19. void dfs(int u,int id){
  20. block[id].push_back(u);
  21. bid[u] = id;
  22. for (int i = 0; i < h[u].size(); i ++ ){
  23. int j = h[u][i].x;
  24. if(!bid[j]){
  25. dfs(j,id);
  26. }
  27. }
  28. }
  29. void dij(int id){
  30. priority_queue<PII,vector<PII>,greater<PII>>heap;
  31. for (int i = 0; i < block[id].size(); i ++ ){
  32. int a = block[id][i];
  33. heap.push({dist[a],a});
  34. }
  35. while(heap.size()){
  36. auto t = heap.top();
  37. heap.pop();
  38. int a = t.y;
  39. int d = t.x;
  40. if(st[a]){
  41. continue;
  42. }
  43. st[a] = true;
  44. for (int i = 0; i < h[a].size(); i ++ ){
  45. int b = h[a][i].x;
  46. int c = h[a][i].y;
  47. if(bid[a]!=bid[b]&&--din[bid[b]]==0){ // 说明这个通过a点可以到达另一个小堆里面
  48. q.push(bid[b]);
  49. }
  50. if(dist[b]>d+c){
  51. dist[b] = d+c;
  52. if(bid[a]==bid[b]){
  53. heap.push({dist[b],b});
  54. }
  55. }
  56. }
  57. }
  58. }
  59. void topsort(){
  60. memset(dist, 0x3f, sizeof dist);
  61. dist[S]=0;
  62. for (int i = 1; i <= cnt; i ++ ){
  63. if(!din[i]){
  64. q.push(i);
  65. }
  66. }
  67. while(!q.empty()){
  68. auto t = q.front();
  69. q.pop();
  70. dij(t);
  71. }
  72. }
  73. int main()
  74. {
  75. cin >> n >> mr >> mp >> S;
  76. for (int i = 0; i < mr; i ++ ){
  77. int a,b,c;
  78. cin >> a >> b >> c;
  79. h[a].push_back({b,c});
  80. h[b].push_back({a,c});
  81. }
  82. // 建立各个小分块
  83. for (int i = 1; i <= n; i ++ ){
  84. if(!bid[i]){
  85. dfs(i,++cnt);
  86. }
  87. }
  88. // 读入航线
  89. for (int i = 0; i < mp; i ++ ){
  90. int a,b,c;
  91. cin >> a >> b >> c;
  92. din[bid[b]]++;
  93. h[a].push_back({b,c});
  94. }
  95. topsort();
  96. for (int i = 1; i <= n; i ++ ){
  97. if(dist[i]>INF/2){
  98. puts("NO PATH");
  99. }else{
  100. cout << dist[i]<<endl;
  101. }
  102. }
  103. }

341. 最优贸易

C 国有 n 个大城市和 m 条道路,每条道路连接这 n 个城市中的某两个城市。
任意两个城市之间最多只有一条道路直接相连。
这 m 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为 1 条。
C 国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。
但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。
商人阿龙来到 C 国旅游
当他得知“同一种商品在不同城市的价格可能会不同”这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚一点旅费。
设 C 国 n 个城市的标号从 1∼n,阿龙决定从 1 号城市出发,并最终在 n 号城市结束自己的旅行。
在旅游的过程中,任何城市可以被重复经过多次,但不要求经过所有 n 个城市。
阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品——水晶球,并在之后经过的另一个城市卖出这个水晶球,用赚取的差价当做旅费。
因为阿龙主要是来 C 国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。
现在给出 n 个城市的水晶球价格,m 条道路的信息(每条道路所连接的两个城市的编号以及该条道路的通行情况)。
请你告诉阿龙,他最多能赚取多少旅费。
输入格式
第一行包含 2 个正整数 n 和 m,中间用一个空格隔开,分别表示城市的数目和道路的数目。
第二行 n 个正整数,每两个整数之间用一个空格隔开,按标号顺序分别表示这 n 个城市的商品价格。
接下来 m 行,每行有 3 个正整数,x,y,z,每两个整数之间用一个空格隔开。
如果 z=1,表示这条道路是城市 x 到城市 y 之间的单向道路;如果 z=2,表示这条道路为城市 x 和城市 y 之间的双向道路。
输出格式
一个整数,表示答案。
输入样例:
5 5
4 3 5 6 1
1 2 1
1 4 1
2 3 2
3 5 1
4 5 2
输出样例:
5
image.png
image.png

  1. #include <iostream>
  2. #include <cstring>
  3. #include <algorithm>
  4. #include <vector>
  5. #include <queue>
  6. using namespace std;
  7. const int N = 1e5+10;
  8. int n,m;
  9. int d1[N],d2[N];
  10. int mind[N],maxd[N];
  11. vector<int>h1[N];
  12. vector<int>h2[N];
  13. int w[N];
  14. bool st[N];
  15. void spfa(vector<int>h[],int dist[],int type){
  16. memset(st, 0, sizeof st);
  17. queue<int>q;
  18. if(type==0){
  19. memset(dist, 0x3f, N*4);
  20. dist[1] = w[1];
  21. q.push(1);
  22. }else{
  23. memset(dist, 0, N*4);
  24. dist[n] = w[n];
  25. q.push(n);
  26. }
  27. while(!q.empty()){
  28. int a = q.front();
  29. q.pop();
  30. st[a] = false;
  31. for (int i = 0; i < h[a].size(); i ++ ){
  32. int b = h[a][i];
  33. if(type==0){
  34. int t = min(w[b],dist[a]);
  35. if(t<dist[b]){
  36. dist[b]=t;
  37. if(!st[b]){
  38. q.push(b);
  39. }
  40. }
  41. }else{
  42. int t = max(w[b],dist[a]);
  43. if(t>dist[b]){
  44. dist[b]=t;
  45. if(!st[b]){
  46. q.push(b);
  47. }
  48. }
  49. }
  50. }
  51. }
  52. }
  53. int main()
  54. {
  55. cin >> n >> m;
  56. for (int i = 1; i <= n; i ++ ){
  57. cin >> w[i];
  58. }
  59. while (m -- ){
  60. int a,b,t;
  61. cin >> a >> b >> t;
  62. h1[a].push_back(b);
  63. h2[b].push_back(a);
  64. if(t==2){
  65. h1[b].push_back(a);
  66. h2[a].push_back(b);
  67. }
  68. }
  69. spfa(h1,mind,0); // 从1号点走到k的最小值
  70. spfa(h2,maxd,1); // 从n号点走的k的最大值
  71. int res=0;
  72. for (int i = 1; i <= n; i ++ ){
  73. res = max(maxd[i]-mind[i],res);
  74. }
  75. cout << res;
  76. }

差分约束

image.pngimage.png
image.png

1169. 糖果

幼儿园里有 N 个小朋友,老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果。
但是小朋友们也有嫉妒心,总是会提出一些要求,比如小明不希望小红分到的糖果比他的多,于是在分配糖果的时候, 老师需要满足小朋友们的 K 个要求。
幼儿园的糖果总是有限的,老师想知道他至少需要准备多少个糖果,才能使得每个小朋友都能够分到糖果,并且满足小朋友们所有的要求。
输入格式
输入的第一行是两个整数 N,K。
接下来 K 行,表示分配糖果时需要满足的关系,每行 3 个数字 X,A,B。
如果 X=1.表示第 A 个小朋友分到的糖果必须和第 B 个小朋友分到的糖果一样多。
如果 X=2,表示第 A 个小朋友分到的糖果必须少于第 B 个小朋友分到的糖果。
如果 X=3,表示第 A 个小朋友分到的糖果必须不少于第 B 个小朋友分到的糖果。
如果 X=4,表示第 A 个小朋友分到的糖果必须多于第 B 个小朋友分到的糖果。
如果 X=5,表示第 A 个小朋友分到的糖果必须不多于第 B 个小朋友分到的糖果。
小朋友编号从 1 到 N。
输出格式
输出一行,表示老师至少需要准备的糖果数,如果不能满足小朋友们的所有要求,就输出 −1。
输入样例:
5 7
1 1 2
2 3 2
4 4 1
3 4 5
5 4 5
2 3 5
4 5 1
输出样例:
11

  1. #include <iostream>
  2. #include <cstring>
  3. #include <algorithm>
  4. #include <vector>
  5. #include <stack>
  6. #define x first
  7. #define y second
  8. typedef long long LL;
  9. using namespace std;
  10. typedef pair<int, int> PII;
  11. const int N = 1e5+10;
  12. int n,m;
  13. vector<PII>h[N];
  14. LL dist[N];
  15. bool st[N];
  16. int cnt[N];
  17. bool spfa(){
  18. // 因为要判断是否有环,所以用stack能更快地发现环
  19. memset(st, 0, sizeof st);
  20. // 求最小糖果数量,求最长路
  21. memset(dist, -0x3f, sizeof dist);
  22. dist[0]=0;
  23. stack<int>s;
  24. s.push(0);
  25. while(!s.empty()){
  26. int a = s.top();
  27. s.pop();
  28. st[a]=false;
  29. for (int i = 0; i < h[a].size(); i ++ ){
  30. int b = h[a][i].x;
  31. int c = h[a][i].y;
  32. if(dist[a]+c>dist[b]){
  33. dist[b] = dist[a]+c;
  34. cnt[b]++;
  35. // 存在环了,如果无环,一个点最多被更新n-1次
  36. if(cnt[b]>=n){
  37. return false;
  38. }
  39. if(!st[b]){
  40. st[b] = true;
  41. s.push(b);
  42. }
  43. }
  44. }
  45. }
  46. return true;
  47. }
  48. int main()
  49. {
  50. cin >> n >> m;
  51. // 建立超级源点,最少数量必须是1
  52. for (int i = 1; i <= n; i ++ ){
  53. h[0].push_back({i,1});
  54. }
  55. for (int i = 0; i < m; i ++ ){
  56. int t,a,b;
  57. cin >> t >> a>>b;
  58. if(t==1){
  59. h[a].push_back({b,0});
  60. h[b].push_back({a,0});
  61. }else if(t==2){
  62. h[a].push_back({b,1});
  63. }else if(t==3){
  64. h[b].push_back({a,0});
  65. }else if(t==4){
  66. h[b].push_back({a,1});
  67. }else{
  68. h[a].push_back({b,0});
  69. }
  70. }
  71. bool res = spfa();
  72. if(res){
  73. LL ans = 0;
  74. for (int i = 1; i <= n; i ++ ){
  75. ans +=dist[i];
  76. }
  77. cout << ans;
  78. }else{
  79. cout << -1 ;
  80. }
  81. }

362.区间

给定 n 个区间 [ai,bi] 和 n 个整数 ci。
你需要构造一个整数集合 Z,使得 ∀i∈[1,n],Z 中满足 ai≤x≤bi 的整数 x 不少于 ci 个。
求这样的整数集合 Z 最少包含多少个数。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含三个整数 ai,bi,ci。
输出格式
输出一个整数表示结果。
数据范围
1≤n≤50000,
0≤ai,bi≤50000,
1≤ci≤bi−ai+1
输入样例:
5
3 7 3
8 10 3
6 8 1
1 3 1
10 11 1
输出样例:
6

这道题题意有点难理解,注意一定是集合

image.png

  1. #include <cstdio>
  2. #include <cstring>
  3. #include <iostream>
  4. #include <algorithm>
  5. using namespace std;
  6. const int N = 50010, M = 150010;
  7. int n;
  8. int h[N], e[M], w[M], ne[M], idx;
  9. int dist[N];
  10. int q[N];
  11. bool st[N];
  12. void add(int a, int b, int c)
  13. {
  14. e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
  15. }
  16. void spfa()
  17. {
  18. memset(dist, -0x3f, sizeof dist);
  19. dist[0] = 0;
  20. st[0] = true;
  21. int hh = 0, tt = 1;
  22. q[0] = 0;
  23. while (hh != tt)
  24. {
  25. int t = q[hh ++ ];
  26. if (hh == N) hh = 0;
  27. st[t] = false;
  28. for (int i = h[t]; ~i; i = ne[i])
  29. {
  30. int j = e[i];
  31. if (dist[j] < dist[t] + w[i])
  32. {
  33. dist[j] = dist[t] + w[i];
  34. if (!st[j])
  35. {
  36. q[tt ++ ] = j;
  37. if (tt == N) tt = 0;
  38. st[j] = true;
  39. }
  40. }
  41. }
  42. }
  43. }
  44. int main()
  45. {
  46. scanf("%d", &n);
  47. memset(h, -1, sizeof h);
  48. for (int i = 1; i < N; i ++ )
  49. {
  50. add(i - 1, i, 0);
  51. add(i, i - 1, -1);
  52. }
  53. for (int i = 0; i < n; i ++ )
  54. {
  55. int a, b, c;
  56. scanf("%d%d%d", &a, &b, &c);
  57. a ++, b ++ ;
  58. add(a - 1, b, c);
  59. }
  60. spfa();
  61. printf("%d\n", dist[50001]);
  62. return 0;
  63. }

1170. 排队布局

当排队等候喂食时,奶牛喜欢和它们的朋友站得靠近些。
农夫约翰有 N 头奶牛,编号从 1 到 N,沿一条直线站着等候喂食。
奶牛排在队伍中的顺序和它们的编号是相同的。
因为奶牛相当苗条,所以可能有两头或者更多奶牛站在同一位置上
如果我们想象奶牛是站在一条数轴上的话,允许有两头或更多奶牛拥有相同的横坐标。
一些奶牛相互间存有好感,它们希望两者之间的距离不超过一个给定的数 L。
另一方面,一些奶牛相互间非常反感,它们希望两者间的距离不小于一个给定的数 D。
给出 ML 条关于两头奶牛间有好感的描述,再给出 MD 条关于两头奶牛间存有反感的描述。
你的工作是:如果不存在满足要求的方案,输出-1;如果 1 号奶牛和 N 号奶牛间的距离可以任意大,输出-2;否则,计算出在满足所有要求的情况下,1 号奶牛和 N 号奶牛间可能的最大距离。
输入格式
第一行包含三个整数 N,ML,MD。
接下来 ML 行,每行包含三个正整数 A,B,L,表示奶牛 A 和奶牛 B 至多相隔 L 的距离。
再接下来 MD 行,每行包含三个正整数 A,B,D,表示奶牛 A 和奶牛 B 至少相隔 D 的距离。
输出格式
输出一个整数,如果不存在满足要求的方案,输出-1;如果 1 号奶牛和 N 号奶牛间的距离可以任意大,输出-2;否则,输出在满足所有要求的情况下,1 号奶牛和 N 号奶牛间可能的最大距离。
数据范围
2≤N≤1000,
1≤ML,MD≤10^4,
1≤L,D≤10^6
输入样例:
4 2 1
1 3 10
2 4 20
2 3 3
输出样例:
27
image.png

  1. #include <iostream>
  2. #include <cstring>
  3. #include <algorithm>
  4. #include <vector>
  5. #include <queue>
  6. #define x first
  7. #define y second
  8. using namespace std;
  9. typedef pair<int, int> PII;
  10. const int N = 1010,INF = 0x3f3f3f3f;
  11. vector<PII>h[N];
  12. bool st[N];
  13. int cnt[N];
  14. int dist[N];
  15. int n,m,k;
  16. bool spfa(int size){
  17. memset(st, 0, sizeof st);
  18. memset(dist ,0x3f, sizeof dist);
  19. memset(cnt, 0, sizeof cnt);
  20. queue<int>q;
  21. for (int i = 1; i <= size; i ++ ){
  22. q.push(i);
  23. st[i] = true;
  24. dist[i] = 0;
  25. }
  26. while(!q.empty()){
  27. int t = q.front();
  28. q.pop();
  29. st[t] = false;
  30. for (int i = 0; i < h[t].size(); i ++ ){
  31. int b = h[t][i].x;
  32. int c = h[t][i].y;
  33. if(dist[b]>dist[t]+c){ // 求最短路
  34. dist[b] = dist[t]+c;
  35. cnt[b]++;
  36. if(cnt[b]>n){
  37. return false;
  38. }
  39. if(!st[b]){
  40. st[b] = true;
  41. q.push(b);
  42. }
  43. }
  44. }
  45. }
  46. return true;
  47. }
  48. int main()
  49. {
  50. cin >> n >> m >> k;
  51. for (int i = 1; i < n; i ++ ){
  52. h[i+1].push_back({i,0});
  53. }
  54. while (m -- ){
  55. int a,b,c;
  56. cin >> a >> b >> c;
  57. if(a>b){
  58. swap(a,b);
  59. }
  60. h[a].push_back({b,c});
  61. }
  62. while (k -- ){
  63. int a,b,c;
  64. cin >> a >> b >> c;
  65. if(a>b){
  66. swap(a,b);
  67. }
  68. h[b].push_back({a,-c});
  69. }
  70. if(!spfa(n)){ // 把n个点全放进去,看看有没负环
  71. puts("-1");
  72. }else{
  73. spfa(1); // 看看1号点能不能到n号点
  74. if(dist[n]==INF){
  75. puts("-2");
  76. }else{
  77. cout << dist[n];
  78. }
  79. }
  80. }

最近公共祖先

image.png
image.png

1172. 祖孙询问

给定一棵包含 nn 个节点的有根无向树,节点编号互不相同,但不一定是 1∼n1∼n。
有 mm 个询问,每个询问给出了一对节点的编号 xx 和 yy,询问 xx 与 yy 的祖孙关系。
输入格式
输入第一行包括一个整数 表示节点个数;
接下来 nn 行每行一对整数 aa 和 bb,表示 aa 和 bb 之间有一条无向边。如果 bb 是 −1−1,那么 aa 就是树的根;
第 n+2n+2 行是一个整数 mm 表示询问个数;
接下来 mm 行,每行两个不同的正整数 xx 和 yy,表示一个询问。
输出格式
对于每一个询问,若 xx 是 yy 的祖先则输出 11,若 yy 是 xx 的祖先则输出 22,否则输出 00。

image.png image.png

  1. #include <iostream>
  2. #include <cstring>
  3. #include <algorithm>
  4. #include <vector>
  5. #include <queue>
  6. using namespace std;
  7. const int N = 4e4+10;
  8. int n,m,root;
  9. int f[N][16],depth[N];
  10. vector<int>h[N];
  11. bool st[N];
  12. void bfs(int u){
  13. queue<int>q;
  14. depth[0] = 0;
  15. depth[u] = 1;
  16. q.push(u);
  17. st[u] = true;
  18. while(!q.empty()){
  19. int a = q.front();
  20. q.pop();
  21. for (int i = 0; i < h[a].size(); i ++ ){
  22. int b = h[a][i];
  23. if(!st[b]){
  24. st[b] = true;
  25. q.push(b);
  26. depth[b] = depth[a]+1;
  27. // 倍增求其祖先
  28. f[b][0] = a;
  29. for (int k = 1; k <= 15; k ++ ){
  30. f[b][k] = f[f[b][k-1]][k-1];
  31. }
  32. }
  33. }
  34. }
  35. }
  36. int lca(int a,int b){
  37. // a的层数更深
  38. if(depth[a]<depth[b]){
  39. swap(a,b);
  40. }
  41. // 先跳到同一层
  42. for (int k = 15; k >=0; k -- ){
  43. if(depth[f[a][k]]>=depth[b]){
  44. a = f[a][k];
  45. }
  46. }
  47. // 如果两者相等了
  48. if(a==b){
  49. return a;
  50. }
  51. // 再跳到公共祖先的下一层,
  52. for (int k = 15; k >=0; k -- ){
  53. if(depth[f[a][k]]!=depth[f[b][k]]){
  54. a = f[a][k];
  55. b = f[b][k];
  56. }
  57. }
  58. return f[a][0];
  59. }
  60. int main()
  61. {
  62. cin >> n ;
  63. for (int i = 0; i < n; i ++ ){
  64. int a,b;
  65. cin >> a >> b;
  66. if(b==-1){
  67. root = a;
  68. }else{
  69. h[a].push_back(b);
  70. h[b].push_back(a);
  71. }
  72. }
  73. bfs(root);
  74. cin >> m;
  75. while (m -- ){
  76. int a,b;
  77. cin >> a >> b;
  78. int p = lca(a,b);
  79. if(p==a){
  80. puts("1");
  81. }else if(p==b){
  82. puts("2");
  83. }else{
  84. puts("0");
  85. }
  86. }
  87. }

1171. 距离

给出 n 个点的一棵树,多次询问两点之间的最短距离。
注意:
边是无向的。
所有节点的编号是 1,2,…,n。
输入格式
第一行为两个整数 n 和 m。n 表示点数,m 表示询问次数;
下来 n−1 行,每行三个整数 x,y,k,表示点 x 和点 y 之间存在一条边长度为 k;
再接下来 m 行,每行两个整数 x,y,表示询问点 x 到点 y 的最短距离。
树中结点编号从 1 到 n。
输出格式
共 m 行,对于每次询问,输出一行询问结果。
输入样例1:
2 2
1 2 100
1 2
2 1
输出样例1:
100
100
输入样例2:
3 2
1 2 10
3 1 15
1 2
3 2
输出样例2:
10
25
image.png
image.png
image.png

  1. #include <iostream>
  2. #include <cstring>
  3. #include <algorithm>
  4. #include <vector>
  5. #include <queue>
  6. #define x first
  7. #define y second
  8. using namespace std;
  9. typedef pair<int, int> PII;
  10. const int N = 4e4+10;
  11. int n,m,root;
  12. vector<PII>h[N];
  13. vector<PII>query[N]; // x存另外一个点,y存查询编号
  14. int res[N];
  15. int d[N]; // i节点到父节点的距离
  16. int st[N];
  17. int p[N];
  18. int find(int x) // 并查集
  19. {
  20. if (p[x] != x) p[x] = find(p[x]);
  21. return p[x];
  22. }
  23. void dfs(int u,int fa){
  24. for (int i = 0; i < h[u].size(); i ++ ){
  25. int son = h[u][i].x;
  26. int w = h[u][i].y;
  27. if(son==fa){
  28. continue;
  29. }
  30. d[son] = d[u] + w;
  31. dfs(son,u);
  32. }
  33. }
  34. void tarjan(int u){
  35. st[u] = 1;
  36. for (int i = 0; i < h[u].size(); i ++ ){
  37. int son = h[u][i].x;
  38. if(st[son]){
  39. continue;
  40. }
  41. tarjan(son);
  42. p[son] = u;
  43. }
  44. for(auto t:query[u]){
  45. int b = t.x;
  46. int index = t.y;
  47. if(st[b]==2){
  48. int fab = find(b);
  49. res[index] = d[u]+d[b]-2*d[fab];
  50. }
  51. }
  52. st[u] = 2;
  53. }
  54. int main()
  55. {
  56. cin >> n >>m;
  57. for (int i = 0; i < n-1; i ++ ){
  58. int a,b,c;
  59. cin >> a >> b >>c;
  60. h[a].push_back({b,c});
  61. h[b].push_back({a,c});
  62. }
  63. for (int i = 0; i < m; i ++){
  64. int a,b;
  65. cin >> a >> b;
  66. query[a].push_back({b,i});
  67. query[b].push_back({a,i});
  68. }
  69. for (int i = 1; i <= n; i ++ ){
  70. p[i] = i;
  71. }
  72. dfs(1,-1);
  73. tarjan(1);
  74. for (int i = 0; i < m; i ++ ){
  75. cout << res[i] <<endl;
  76. }
  77. }

有向图的强连通分量

1174. 受欢迎的牛

每一头牛的愿望就是变成一头最受欢迎的牛。
现在有 N 头牛,编号从 1 到 N,给你 M 对整数 (A,B),表示牛 A 认为牛 B 受欢迎。
这种关系是具有传递性的,如果 A 认为 B 受欢迎,B 认为 C 受欢迎,那么牛 A 也认为牛 C 受欢迎。
你的任务是求出有多少头牛被除自己之外的所有牛认为是受欢迎的。
输入格式
第一行两个数 N,M;
接下来 M 行,每行两个数 A,B,意思是 A 认为 B 是受欢迎的(给出的信息有可能重复,即有可能出现多个 A,B)。
输出格式
输出被除自己之外的所有牛认为是受欢迎的牛的数量。
数据范围
1≤N≤10^4,
1≤M≤5×10^4
输入样例:
3 3
1 2
2 1
2 3
输出样例:
1
样例解释
只有第三头牛被除自己之外的所有牛认为是受欢迎的。

image.png
image.png
image.png
image.png

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. const int N = 10010, M = 50010;
  4. int n, m;
  5. int h[N], w[M], e[M], ne[M], idx; // 邻接表一套
  6. // dfn[u] 存的是遍历到u的时间戳
  7. // low[u]存的是从u出发,遍历子树,所能遍历到的最小的时间戳
  8. //timestamp 就是时间戳
  9. int dfn[N], low[N], timestamp;
  10. int stk[N], top; // 栈,和栈顶元素索引
  11. bool in_stk[N]; // 是否在栈中
  12. //id[u]表示u的强连通分量的编号,scc_cnt表示强连通分量的编号
  13. // size_scc[u]表示编号为u强连通分量中点的数量
  14. int id[N], scc_cnt, size_scc[N];
  15. int dout[N];// 记录新图中每个点(也就是原图每个连通分量)的出度
  16. void add(int a, int b){
  17. e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
  18. }
  19. void tarjan(int u){
  20. //当前点的时间戳
  21. dfn[u] = low[u] = ++ timestamp;
  22. // 加入栈中
  23. stk[++ top] = u, in_stk[u] = true;
  24. //遍历u点的所有邻点
  25. for(int i = h[u]; ~i; i = ne[i]){
  26. int j = e[i];
  27. if(!dfn[j]){//如果没有遍历过
  28. tarjan(j); // 遍历它
  29. low[u] = min(low[u], low[j]);
  30. }
  31. // 当前点在栈当中
  32. else if(in_stk[j]) low[u] = min(low[u], dfn[j]);
  33. }
  34. if(dfn[u] == low[u]){
  35. ++ scc_cnt; // 更新强连通分量的编号
  36. int y;
  37. do{
  38. y = stk[ top--]; //不断取出栈内元素
  39. in_stk[y] = false;
  40. id[y] = scc_cnt; //y元素所属的连通块编号
  41. size_scc[scc_cnt] ++; //该连通块内包含的点数
  42. }while(y != u); // 直到y不等于u
  43. }
  44. }
  45. int main(){
  46. cin >> n >> m;
  47. memset(h, -1, sizeof h);
  48. while(m --){
  49. int a, b;
  50. cin >> a >> b;
  51. add(a, b);
  52. }
  53. for(int i = 1; i <= n; i ++){
  54. if(!dfn[i])
  55. tarjan(i);
  56. }
  57. // 建有向无环图
  58. // 统计在新图中所有点的出度
  59. for(int i = 1; i <= n; i ++){
  60. for(int j = h[i]; ~j; j = ne[j]){
  61. int k = e[j];
  62. int a = id[i]; //a表示i所在连通分量的编号
  63. int b = id[k]; // b表示k所在连通分量的编号
  64. //如果点i和点k不在同一个连通块
  65. // dout存的是出度,因为本题只需要出度
  66. //在其他题目中,可能是要建边,因为这里是构造有向无环图
  67. if(a != b) dout[a] ++; // 从a走到b,a的出度++
  68. }
  69. }
  70. // 和本题有关的部分:
  71. // zeros是统计在新图中,出度为0的点的个数
  72. // sum表示满足条件的点(最受欢迎的奶牛)的个数
  73. int zeros = 0, sum = 0;
  74. for(int i = 1; i <= scc_cnt; i ++){
  75. if(!dout[i]){
  76. zeros ++;
  77. sum += size_scc[i];
  78. if(zeros > 1){
  79. sum = 0;
  80. break;
  81. }
  82. }
  83. }
  84. cout << sum << endl;
  85. }