建图技巧
反向建图
注意,既然提到了反向,所以这个技巧只适用于有向图。
1.在单源最短路问题中:由单到多变为由多到单
例题
Silver Cow Party
题意:
给出n,m,代表牛数和边数,接着是m条有向边,代表从牛a到牛b需要花费c时间,现在所有牛要到牛x那里去参加聚会,并且所有牛参加聚会后还要回来,给你牛x,除了牛x之外的牛,他们都有一个参加聚会并且回来的最短时间,从这些最短时间里找出一个最大值输出 N<=1000,M<=100,000
Input
第 1 行:分别有三个空格分隔的整数:N、M 和 X
行 2。M+1:i+1 行用三个空格分隔的整数描述道路 i:Ai、Bi
和 Ti。所描述的道路从农场Ai延伸到农场Bi,需要Ti时间单位才能穿越。
Output
第 1 行:一个整数:任何一头奶牛必须行走的最长时间。
Sample Input
4 8 2
1 2 4
1 3 2
1 4 7
2 1 1
2 3 5
3 1 2
3 4 4
4 2 3
Sample Output
10
分析
给了一个特殊点x而且每头牛都有一个最短时间,很明显是单源最短路问题。从数据范围来看,稠密图,权值全为正,朴素版dijkstra。难点是我们跑一遍dijkstra算法只能从起点找出到其他点的最短距离,题目中的牛儿们却有来有回,抽象出模型来便是从众多点向x点汇聚,再从x点回到原处。第二个过程是我们熟悉的单到多的过程,而第一个过程却是一个多到单的过程。
所以利用反向建图:
额外建立一个有向图,该图中所有的边都与原图方向相反,权值不变.
在这张图中再次使用Dijkstra求出从节点x到任意点的最短距离.
在这张图上得到的最短距离dist[i]就是在原图上从节点i到节点x的最短距离.
ac代码
#include<iostream>
#include<cstring>
#include<aLgorithm>
#include<vector>
using namespace std;
const int N = 1010;
bool st[N];
int n,m,s;
int g[N][N];
int w[N],dist[N];
struct Edge
{
int a,b,c;
};
void dijkstra()
{
memset(st,0,sizeof st);
memset(dist,0x3f,sizeof dist);
dist[s] = 0;
for(int i = 0;i < n;i ++)
{
int t = -1;
for(int j = 1;j <= n;j ++)
if(!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
st[t] = true;
for(int j = 1;j <= n;j ++) dist[j] = min(dist[j],dist[t] + g[t][j]);
}
}
vector<Edge> edges;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
memset(g,0x3f,sizeof g);
cin >> n >> m >> s;
while(m --)
{
int a,b,c;
cin >> a >> b >> c;
edges.push_back({b,a,c});
g[a][b] = min(g[a][b],c);
}
dijkstra();
for(int i = 1;i <= n;i ++) w[i] = dist[i];
memset(g,0x3f,sizeof g);//建反向边
for(vector<Edge>::iterator it = edges.begin();it != edges.end();it ++)
{
int a = (*it).a,b = (*it).b,c = (*it).c;
g[a][b] = min(g[a][b],c);
}
dijkstra();
int res = 0;
for(int i = 1;i <= n;i ++) res = max(res,dist[i] + w[i]);
cout << res << endl;
return 0;
}
2.正难则反
例题
图的遍历
题目描述
给出N个点,M条边的有向图,对于每个点v,求表示从点v出发,能到达的编号最大的点。
输入格式
第1 行,2 个整数N,M。
接下来M行,每行2个整数U i ,V i ,表示边。点用1,2,⋯,N编号。
输出格式
N 个整数。
输入输出样例
4 3
1 2
2 4
4 3
4 4 3 4
说明/提示
• 对于60% 的数据,;
• 对于100% 的数据, 。
分析
的数据 我们从一开始对每个点进行dfs肯定是不行的,这时反向建图,逆向思维,从最大的节点搜能够遍历到的点能到达节点编号最大的必是当前节点。
ac代码
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
bool st[N];
int h[N],e[N],ne[N],idx;
int n,m;
int ans[N];
void add(int a,int b)
{
e[idx] = b,ne[idx] = h[a],h[a] = idx ++;
}
void dfs(int u,int c)
{
ans[u] = max(c,ans[u]);
for(int i = h[u];i != -1;i = ne[i])
{
int j = e[i];
if(!st[j])
{
st[j] = true;
dfs(j,c);
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
memset(h,-1,sizeof h);
cin >> n >> m;
while(m --)
{
int a,b;
cin >> a >> b;
add(b,a);
}
for(int i = n;i;i --) dfs(i,i);
for(int i = 1;i <= n;i ++) cout << ans[i] << ' ';
return 0;
}
切记反向建图只能用于有向图
虚拟节点
用一个题目定义外的虚拟节点控制多个节点
例题
POJ : 昂贵的聘礼
年轻的探险家来到了一个印第安部落里。在那里他和酋长的女儿相爱了,于是便向酋长去求亲。酋长要他用10000个金币作为聘礼才答应把女儿嫁给他。探险家拿不出这么多金币,便请求酋长降低要求。酋长说:"嗯,如果你能够替我弄到大祭司的皮袄,我可以只要8000金币。如果你能够弄来他的水晶球,那么只要5000金币就行了。"探险家就跑到大祭司那里,向他要求皮袄或水晶球,大祭司要他用金币来换,或者替他弄来其他的东西,他可以降低价格。探险家于是又跑到其他地方,其他人也提出了类似的要求,或者直接用金币换,或者找到其他东西就可以降低价格。不过探险家没必要用多样东西去换一样东西,因为不会得到更低的价格。探险家现在很需要你的帮忙,让他用最少的金币娶到自己的心上人。另外他要告诉你的是,在这个部落里,等级观念十分森严。地位差距超过一定限制的两个人之间不会进行任何形式的直接接触,包括交易。他是一个外来人,所以可以不受这些限制。但是如果他和某个地位较低的人进行了交易,地位较高的的人不会再和他交易,他们认为这样等于是间接接触,反过来也一样。因此你需要在考虑所有的情况以后给他提供一个最好的方案。
为了方便起见,我们把所有的物品从1开始进行编号,酋长的允诺也看作一个物品,并且编号总是1。每个物品都有对应的价格P,主人的地位等级L,以及一系列的替代品Ti和该替代品所对应的"优惠"Vi。如果两人地位等级差距超过了M,就不能"间接交易"。你必须根据这些数据来计算出探险家最少需要多少金币才能娶到酋长的女儿。
Input
输入第一行是两个整数M,N(1 <= N <= 100),依次表示地位等级差距限制和物品的总数。接下来按照编号从小到大依次给出了N个物品的描述。每个物品的描述开头是三个非负整数P、L、X(X < N),依次表示该物品的价格、主人的地位等级和替代品总数。接下来X行每行包括两个整数T和V,分别表示替代品的编号和"优惠价格"。
Output
输出最少需要的金币数。
Sample
Input
1 4
10000 3 2
2 8000
3 5000
1000 2 1
4 200
3000 2 1
4 200
50 2 0
Output
5250
分析:
首先不考虑等级限制,本题要求最终走到酋长这个点(不妨设为1号) ,那么我们获得的一件物品的方法无外乎两种:1.直接买、2.用已有物品加上一些钱换,尝试用图的方式来表达:a可用物品b换就对应着b到a有一条有向边,边权就是替代的价值,直接买某个物品可以对应着一个虚拟节点(不妨设为0)向这个物品连一条边,边权为该物品直接买的价值。那么在不考虑等级限制的条件下,我们如果要求得到1的最小花费就等价于从虚拟节点到一号点的最小距离,转化为最短路问题求解,再考虑等级限制,我们可以枚举每个等级区间,每次求最短路是只能更新在这个区间里面的物品。枚举所有情况求一个最小值就可以了。 特别注意的是区间必须包含1点。 那么范围就是
ac代码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<stack>
#include<set>
#include<deque>
#include <sstream>
#define ios ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#define endl '\n'
#define pi 3.14159265358979323846
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
const int N = 110,INF = 0x3f3f3f3f,mod = 998244353;
const double INFF = 0x7f7f7f7f7f7f7f7f;
int g[N][N];
int dist[N],level[N];
bool st[N];
int m,n;
int dijkstra(int l,int r)
{
memset(dist,0x3f,sizeof dist);
memset(st,0,sizeof st);
dist[0] = 0;
for(int i = 0;i < n;i ++)
{
int t = -1;
for(int j = 0;j <= n;j ++)
if(!st[j] && (t == -1 || dist[t] > dist[j])) t = j;
st[t] = true;
for(int j = 0;j <= n;j ++)
if(level[j] >= l && level[j] <= r)
dist[j] = min(dist[j],dist[t] + g[t][j]);
}
return dist[1];
}
int main()
{
ios;
memset(g,0x3f,sizeof g);
cin >> m >> n;
for(int i = 1;i <= n;i ++)
{
int price,cnt;
cin >> price >> level[i] >> cnt;
g[0][i] = min(g[0][i],price);
while(cnt --)
{
int id,cost;
cin >> id >> cost;
g[id][i] = min(g[id][i],cost);
}
}
int res = INF;
for(int i = level[1] - m;i <= level[1];i ++)
{
res = min(res,dijkstra(i,i + m));
}
cout << res << endl;
return 0;
}
HDU:一个人的旅行
虽然草儿是个路痴(就是在杭电待了一年多,居然还会在校园里迷路的人,汗~),但是草儿仍然很喜欢旅行,因为在旅途中 会遇见很多人(白马王子,0),很多事,还能丰富自己的阅历,还可以看美丽的风景……草儿想去很多地方,她想要去东京铁塔看夜景,去威尼斯看电影,去阳明山上看海芋,去纽约纯粹看雪景,去巴黎喝咖啡写信,去北京探望孟姜女……眼看寒假就快到了,这么一大段时间,可不能浪费啊,一定要给自己好好的放个假,可是也不能荒废了训练啊,所以草儿决定在要在最短的时间去一个自己想去的地方!因为草儿的家在一个小镇上,没有火车经过,所以她只能去邻近的城市坐火车(好可怜啊~)。
Input
输入数据有多组,每组的第一行是三个整数T,S和D,表示有T条路,和草儿家相邻的城市的有S个,草儿想去的地方有D个;
接着有T行,每行有三个整数a,b,time,表示a,b城市之间的车程是time小时;(1=<(a,b)<=1000;a,b 之间可能有多条路)
接着的第T+1行有S个数,表示和草儿家相连的城市;
接着的第T+2行有D个数,表示草儿想去地方。
Output
输出草儿能去某个喜欢的城市的最短时间。
Input
6 2 3
1 3 5
1 4 7
2 8 12
3 8 4
4 9 12
9 10 2
1 2
8 9 10
Output
9
分析:
题目可抽象为从给定的某个起点到某个终点的最短路问题,貌似是多源最短路,单数据范围为 1000, 的floyd肯定不行,转换思路:建立一个超级源点连接所有的起点,边权为0,再建立一个超级汇点,所有的终点连向这个汇点,那么题目就等价于从源点到汇点的最短路,跑一遍spfa即可dijkstra也可、
ac代码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<stack>
#include<set>
#include<deque>
#include <sstream>
#include <unordered_map>
#define ios ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#define endl '\n'
#define pi 3.14159265358979323846
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
const int N = 10010,M = 500010,INF = 0x3f3f3f3f,mod = 1e9 + 7 ;
const double INFF = 0x7f7f7f7f7f7f7f7f;
int h[N],e[N],ne[N],idx,w[N];
int t,s,d,n;
int dist[N];
bool st[N];
void add(int a,int b,int c)
{
e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx ++;
}
int spfa()
{
memset(dist,0x3f,sizeof dist);
dist[0] = 0;
queue<int> q;
q.push(0);
st[0] = 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];
if(!st[j])
{
st[j] = true;
q.push(j);
}
}
}
}
return dist[n + 1];
}
int main()
{
//ios;
while(scanf("%d%d%d",&t,&s,&d) != EOF)
{
memset(h,-1,sizeof h);
memset(w,0,sizeof w);
for(int i = 0;i < t;i ++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c),add(b,a,c);
n = max(n,max(a,b));
}
for(int i = 0;i < s;i ++)
{
int a;
scanf("%d",&a);
add(0,a,0);
}
for(int i = 0;i < d;i ++)
{
int a;
scanf("%d",&a);
add(a,n + 1,0);
}
printf("%d\n",spfa());
}
return 0;
}
HDU :The Shortest Path in Nya Graph
这是一个非常简单的问题,你的任务只是计算el camino mas corto en un grafico,只是独奏干草que cambiar un poco el algoritmo。如果您不理解本段的一个字,请继续前进。
Nya 图是带有“层”的无向图。图中的每个节点都属于一个层,总共有N个节点。
您可以从 x 层中的任何节点移动到 x + 1 层中的任何节点,成本为 C,因为道路是双向的,因此也允许以相同的成本从 x + 1 层移动到 x 层。
此外,还有M条额外的边,每条边连接一对节点u和v,成本为w.
帮助我们计算从节点1到节点N的最短路径。
输入
第一行有一个数字T(T <= 20),表示测试用例的数量。
对于每个测试用例,第一行有三个数字N,M(0 <= N,M <= 10 5)和C(1 <= C <= 10 3),这是节点的数量,额外边缘的数量和在相邻层之间移动的成本。
第二行有N个数字l i(1 <= l i <= N),这是第i个节点所属的层。
然后出现N条线,每条线有3个数字,u,v(1 <= u,v < = N,u <> v)和w(1 <= w <= 10 4),这意味着有一个额外的边缘,连接一对节点u和v,成本为w。
输出
对于测试用例 X,首先输出“案例#X:”,然后输出从节点 1 移动到节点 N 的最小成本。
如果没有解,则输出 -1。
Sample Input
3 3 3
1 3 2
1 2 1
2 3 1
1 3 3
3 3 3
1 3 2
1 2 2
2 3 2
1 3 4
Sample Output
Case #1: 2
Case #2: 3
分析:本题是一个裸的最短路问题,难点在建图方面,由于数据范围过大,不能暴力的给每两层的点逐个建边,可以考虑为每层建一个虚拟节点,每层的虚拟节点与该层中的每个节点都有一条边权为0的边,相邻层之间的虚拟节点节点连上一条边权为c的边,这样,相邻层直接a点与b点之间的路径被我们转化为a -> c[a] -> c[b] -> b,c[i]表示,i所在层的虚拟节点,最后跑一遍dijkstra即可。
ac代码
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#include<map>
#define ios ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#define endl '\n'
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
const int N = 1000100,M = 200010,INF = 0x3f3f3f3f;
const double INFF = 0x7f7f7f7f7f7f7f7f;
int h[N],e[N],w[N],ne[N],idx;
int n,m,c;
bool st[N];
int t;
int dist[N];
void add(int a,int b,int c)
{
e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx ++;
}
int dijkstra()
{
memset(st,0,sizeof st);
memset(dist,0x3f,sizeof dist);
dist[1] = 0;
priority_queue<PII,vector<PII>,greater<PII> > heap;
heap.push({0,1});
while(heap.size())
{
auto t = heap.top();
heap.pop();
int node = t.second,d = t.first;
if(st[node]) continue;
st[node] = true;
for(int i = h[node];i != -1;i = ne[i])
{
int j = e[i];
if(dist[j] > d + w[i])
{
dist[j] = d + w[i];
heap.push({dist[j],j});
}
}
}
if(dist[n] == INF) return -1;
else return dist[n];
}
void solve()
{
memset(h,-1,sizeof h);
idx = 0;
cin >> n >> m >> c;
for(int i = 1;i <= n;i ++)
{
int x;
cin >> x;
add(n + x,i,0);
if(x + 1 <= n) add(n + x + 1,i,c),add(i,n + x + 1,c);
if(x - 1 >= 1) add(n + x - 1,i,c),add(i,n + x - 1,c);
}
while(m --)
{
int a,b,c;
cin >> a >> b >> c;
add(a,b,c),add(b,a,c);
}
cout << "Case #" << ++ t << ": " << dijkstra() << endl;
}
int main()
{
ios;
int t;
cin >> t;
while(t --)
{
solve();
}
return 0;
}
本文作者:notyour_young
本文链接:https://www.cnblogs.com/notyour-young/p/16218755.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步