题目链接:kuangbin专题
dijk: 1 7 4 10 3 2 16
spfa:14 12 13 5 15 18
floyd:8 6 9
差分约束:19 11
最后剩下一个第17题,涉及网络流,留在网络流专题做。
这是我做完一遍之后觉得比较好的做题顺序,一个一个知识点学习,每个知识点大致上由易到难,相同类型题放在一起加深理解。
A - Til the Cows Come Home POJ - 2387
题解:就是很裸的最短路。可以拿来试板子。
题解:最短路的变形。
容易得到松弛条件:dis[ i ] = min(dis[ i ], max(dis[ k ], arr[ k ][ j ]))
dis数组所代表的意义改变(代表s点到其他点的所经过的最短路径中的最大边的最小值)
这里顺便说说优先队列的意义,因为每次都是拿所有花费最小的那个点去松弛其他点,
这样就保证了起点到其他点的距离是当前最小。(这个是有具体的证明的,但是我比较懒)
之后这道题是求最大里的最小,既然取最小的点来松弛已经保证了最小,那么只需要记录最大即可。
这里如果难理解的话就这么记吧,松弛所保证的是最终所要的,
比如最大的最小值,那么松弛所保证的就是最小值,那么就只需要 取最大值来比即可。
之后输出格式需要注意,每个例子后面总是会有一行空行。
#include <iostream> #include <cstring> #include <cstdio> #include <algorithm> #include <queue> #include <cmath> #include <vector> #define ll long long #define pii pair<double, int> const int inf = 0x3f3f3f3f; const int mod = 1e9+7; const int maxn = 2e5+7; using namespace std; struct node {int to,next;double w;} edge[maxn]; int head[maxn], cnt, vis[maxn];; double dis[maxn]; int n, m, s, t; void init() { memset(head,-1,sizeof(head)); memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); cnt = 0; } void add_edge(int u,int v,double w) { edge[cnt].to = v; edge[cnt].w = w; edge[cnt].next = head[u]; head[u] = cnt ++; } void dijkstra() { priority_queue<pii,vector<pii>,greater<pii> > q;//从小到大 dis[s] = 0; q.push({dis[s],s}); while(!q.empty()) { int now = q.top().second; q.pop(); if(vis[now]) continue; vis[now] = 1; for(int i = head[now]; i != -1; i = edge[i].next) { int v = edge[i].to; if(dis[v] > max(dis[now], edge[i].w)) { dis[v] = max(dis[now], edge[i].w); q.push({dis[v],v}); } } } } int main() { int num = 0; while (~scanf("%d", &n) && n) { init(); int x[maxn], y[maxn]; for(int i = 1; i <= n; ++i) { dis[i] = inf; scanf("%d%d",&x[i], &y[i]); for (int j = 1; j < i; ++j) { double w = sqrt((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j])); add_edge(i, j, w), add_edge(j, i, w); } } s = 1, t = 2;//s起点,t终点 dijkstra(); printf("Scenario #%d\n", ++num); printf("Frog Distance = %.3f\n\n", dis[t]); } }
C - Heavy Transportation POJ - 1797
题解:这道题与上一道题类似。但是所求的变成了最小的最大值。
松弛条件:dis[ i ] = max(dis[ i ], min(dis[ k ], arr[ k ][ j ]))
那么就要每次取最大的来松弛,所以dis数组初始化都为0,而不是INF,之后将s的dis变为INF,
松弛的时候就取最小值。
#include <iostream> #include <cstring> #include <cstdio> #include <algorithm> #include <queue> #include <cmath> #include <vector> #define ll long long #define pii pair<int, int> const int inf = 0x3f3f3f3f; const int mod = 1e9+7; const int maxn = 2e5+7; using namespace std; struct node {int to,next,w;} edge[maxn]; int head[maxn], cnt, vis[maxn];; int dis[maxn]; int n, m, s, t; void init() { memset(head,-1,sizeof(head)); memset(dis,0,sizeof(dis)); memset(vis,0,sizeof(vis)); cnt = 0; } void add_edge(int u,int v,int w) { edge[cnt].to = v; edge[cnt].w = w; edge[cnt].next = head[u]; head[u] = cnt ++; } void dijkstra() { priority_queue<pii,vector<pii>,less<pii> > q;//从小到大 dis[s] = inf; q.push({dis[s],s}); while(!q.empty()) { int now = q.top().second; q.pop(); if(vis[now]) continue; vis[now] = 1; for(int i = head[now]; i != -1; i = edge[i].next) { int v = edge[i].to; if(dis[v] < min(dis[now], edge[i].w)) { dis[v] = min(dis[now], edge[i].w); q.push({dis[v],v}); } } } } int main() { int T; scanf("%d", &T); for (int z = 1; z <= T; ++z) { init(); scanf("%d%d", &n, &m); for(int i = 1; i <= m; ++i) { int u, v, w; scanf("%d%d%d",&u, &v, &w); add_edge(u, v, w), add_edge(v, u, w); } s = 1, t = n;//s起点,t终点 dijkstra(); printf("Scenario #%d:\n", z); printf("%d\n\n", dis[t]); } }
题解:这题算是个技巧吧。就是建反图求来回路径,并将正图反图存到两个图里,跑两遍dij。
仔细想想也是好想,如果存在一条1到n的路径,那么反着建就是从n到1。这就算是涨个知识。
#include <iostream> #include <cstring> #include <cstdio> #include <algorithm> #include <queue> #include <cmath> #include <vector> #define ll long long #define pii pair<int, int> const int inf = 0x3f3f3f3f; const int mod = 1e9+7; const int maxn = 2e5+7; using namespace std; struct node {int to,next;int w;} edge[2][maxn]; int head[2][maxn], cnt, vis[maxn]; int dis[2][maxn]; int n, m, s, t; void init() { memset(head,-1,sizeof(head)); memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); cnt = 0; } void add_edge(int u,int v,int w,int num) { edge[num][cnt].to = v; edge[num][cnt].w = w; edge[num][cnt].next = head[num][u]; head[num][u] = cnt ++; } void dijkstra(int num) { memset(&dis[num],inf,sizeof(dis)/2); memset(vis,0,sizeof(vis)); priority_queue<pii,vector<pii>,greater<pii> > q;//从小到大 dis[num][s] = 0; q.push({dis[num][s],s}); while(!q.empty()) { int now = q.top().second; q.pop(); if(vis[now]) continue; vis[now] = 1; for(int i = head[num][now]; i != -1; i = edge[num][i].next) { int v = edge[num][i].to; if(dis[num][v] > dis[num][now] + edge[num][i].w) { dis[num][v] = dis[num][now] + edge[num][i].w; q.push({dis[num][v],v}); } } } } int main() { while (cin >> n >> m >> t) { init(); for(int i = 1; i <= m; ++i) { int u, v, w; cin >> u >> v >> w; add_edge(u, v, w, 0); add_edge(v, u, w, 1); //反相建边 } s = t;//s起点 dijkstra(0); dijkstra(1); int ans = 0; for (int i = 1; i <= n; ++i) { ans = max(ans, dis[0][i]+dis[1][i]); } cout << ans << endl; } }
E - Currency Exchange POJ - 1860
题解:算是个找环的裸题。因为找的是正环,所以dis一开始初始化为0。
#include<stdio.h> #include<iostream> #include<algorithm> #include<string.h> #include<vector> #include<cmath> #include<string> #include<map> #include<queue> using namespace std; typedef long long ll; const int INF = 0x3f3f3f3f; // 2122219134 const int maxn = 500 + 10; const int maxm = 100000 + 10; struct E { int to, nex; double r, c; }edge[maxn*2]; int tot,n,m,s; int head[maxn], cnt[maxn]; double dis[maxn],v; bool vis[maxn], cir[maxn]; void dfs(int u) { cir[u] = true; //cir用于标记是否被负环影响 for(int i = head[u]; ~i; i = edge[i].nex) { int v = edge[i].to; if (!cir[v]) dfs(v); } } void init(){ memset(cnt, 0, sizeof(cnt));//cnt[i]记录i入队的次数 memset(dis, 0, sizeof(dis));//初始化dis memset(head, -1, sizeof(head)); memset(vis, 0, sizeof(vis)); memset(cir, 0, sizeof(cir)); tot = 0; } inline void add_edge(int u, int v, double r, double c) { edge[tot].to = v; edge[tot].r = r; edge[tot].c = c; edge[tot].nex = head[u]; head[u] = tot++; } bool bfs_spfa() { dis[s] = v; queue<int> q; q.push(s); vis[s] = 1; cnt[s] = 1; while (!q.empty()) { int u = q.front(); q.pop(); vis[u] = 0; for(int i = head[u]; i != -1; i = edge[i].nex){ int v = edge[i].to; if(dis[v] < (dis[u] - edge[i].c) * edge[i].r) { dis[v] = (dis[u] - edge[i].c) * edge[i].r; if (!vis[v]) { q.push(v); vis[v] = 1; ++cnt[v]; if(cnt[v] >= n) return 1; } } } } return 0; } int main(){ ios::sync_with_stdio(0); cin.tie(0); while (cin >> n >> m >> s >> v) { init(); for (int i = 0; i < m; ++i) { int u, v; double rab, rba, cab, cba; cin >> u >> v >> rab >> cab >> rba >> cba; add_edge(u, v, rab, cab); add_edge(v, u, rba, cba); } if (bfs_spfa()) cout << "YES" << endl; else cout << "NO" << endl; } return 0; }
题解:跟上一题差不多。但是找的是负环,dis要初始化为inf。
#include<stdio.h> #include<iostream> #include<algorithm> #include<string.h> #include<vector> #include<cmath> #include<string> #include<map> #include<queue> using namespace std; typedef long long ll; #define INF 0x3f3f3f3f struct node { ll id; ll d; ll next_s; }side[6000]; ll cnt,n,head[501],flag[501],dis[501],cn[501]; ll book; void init(){ memset(head,-1,sizeof(head)); cnt=0; } void add(ll x,ll y,ll z){ side[cnt].id=y; side[cnt].d=z; side[cnt].next_s=head[x]; head[x]=cnt++; } ll bfs_spfa(){ memset(cn,0,sizeof(cn));//cn[i]记录i入队的次数 memset(dis,INF,sizeof(dis));//初始化dis dis[1]=0; queue<ll> p; //压入那个点,看dis的含义 p.push(1); flag[1]=1; while(p.size()){ ll t=p.front(); p.pop(); flag[t]=0; for(int i=head[t];i!=-1;i=side[i].next_s){ if(dis[side[i].id]>dis[t]+side[i].d){ dis[side[i].id]=dis[t]+side[i].d; if(!flag[side[i].id]){ p.push(side[i].id); flag[side[i].id]=1; cn[side[i].id]++; //接下来和求单源最短路的不同的地方 if(cn[side[i].id]>=n){ //关键不能是大于,是大于等于或等于 return 1; } } } } } return 0; } int main(){ ll f; ios::sync_with_stdio(0); cin.tie(0); cin>>f; while(f--){ memset(dis,INF,sizeof(dis)); memset(flag,0,sizeof(flag)); init(); ll m,w; cin>>n>>m>>w; for(int i=0;i<m;i++){ ll x,y,z; cin>>x>>y>>z; add(x,y,z); add(y,x,z); } for(int i=0;i<w;i++){ ll x,y,z; cin>>x>>y>>z; add(x,y,z*(-1)); } book=bfs_spfa(); if(book==1){ cout<<"YES"<<endl; } else cout<<"NO"<<endl; } return 0; }
题解:这题主要是题面比较恶心,就是求1到各个点的最短路的最大值。就是个裸的dij
H - Cow Contest POJ - 3660
题解:这一题我一开始想用并查集写的,但是放在最短路专题,就去网上看了一眼题解。
这题是Floyd传递闭包。因为题目保证没有矛盾关系,所以一只牛如果跟其他n-1只牛有关系的话,那么这只牛的排名就确定了。
传递闭包:若A->B, B->C通过传递闭包可以得到A->C(不懂得可以去看看离散数学
所以先经过Floyd处理关系。之后统计每只牛的关系数量。等于n-1那么就确定了排名。
#include <iostream> #include <cstring> #include <cstdio> #include <algorithm> #include <queue> #include <cmath> #include <vector> #define ll long long #define pii pair<int, int> using namespace std; const int inf = 0x3f3f3f3f; const int N = 1e3+10; //struct node { // int to,next,w; //} edge[2][maxn]; //int head[2][maxn], cnt[2], vis[maxn]; //int dis[2][maxn]; int n, m, s, t; int g[N][N]; inline ll read() { ll x = 0, f = 1; char ch = getchar(); while (ch<'0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while ( ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); } return x * f; } void init() { for (int i = 1; i <= n; ++ i) for (int j = 1; j <= n; ++ j) g[i][j] = inf; } void Floyd() { for (int k = 1; k <= n; ++ k) { for (int i = 1; i <= n; ++ i) { if (g[i][k] == inf) continue; for (int j = 1; j <= n; ++ j) { if (g[k][j] == inf) continue; if (g[i][k] == g[k][j]) g[i][j] = g[k][j]; } } } } int main() { while (~scanf("%d%d", &n, &m)) { init(); for(int i = 1; i <= m; ++i) { int u, v; u = read(), v = read(); g[u][v] = 1, g[v][u] = -1; // add_edge(u, v, w, 0); // add_edge(v, u, w, 1); //反相建边 } // s = 1;//s起点 // dijkstra(0), dijkstra(1); // ll ans = 0; Floyd(); int res = 0; for (int i = 1; i <= n; ++i) { int temp = 0; for (int j = 1; j <= n; ++j) { if (g[i][j] != inf) ++ temp; } if (temp == n - 1) ++ res; } printf("%d\n", res); } }
I - Arbitrage POJ - 2240
题解:这题跟之前那道换钱差不多。就是需要先用map来hash字符之后就一样的解法。
#include <iostream> #include <cstdio> #include <cmath> #include <cstring> #include <string> #include <map> using namespace std; const int N = 50 + 10; const int M = 1000000 + 10; double dist[N]; int head[N]; int n, m, s, tot; struct E { int to, next; double r; }edge[M*2]; inline void add_edge(int u, int v, double r) { edge[tot].to = v; edge[tot].r = r; edge[tot].next = head[u]; head[u] = tot++; } bool BellmanFord() { dist[s] = 1; for (int i = 0; i < n - 1; ++ i){ for (int u = 1; u <= n; ++ u){ if (dist[u] == 0) continue; for (int k = head[u]; ~k; k = edge[k].next) if (dist[edge[k].to] < dist[u] * edge[k].r) dist[edge[k].to] = dist[u] * edge[k].r; } } for (int u = 1; u <= n; ++ u){ if (dist[u] == 0) continue; for (int k = head[u]; ~k; k = edge[k].next) if (dist[edge[k].to] < dist[u] * edge[k].r) return false; } return true; } void init() { tot = 0; memset(head, -1, sizeof(head)); memset(dist, 0, sizeof(dist)); } int main() { ios::sync_with_stdio(0); cin.tie(0); // freopen("in.txt","r",stdin); //输入重定向,输入数据将从in.txt文件中读取 // freopen("out.txt","w",stdout); //输出重定向,输出数据将保存在out.txt文件中 int cnt = 0; while (cin >> n && n) { init(); map<string, int> dic; for (int i = 1; i <= n; ++i) { string temp; cin >> temp; dic[temp] = i; } cin >> m; for (int i = 0; i < m; ++i) { string u, v; double rate; cin >> u >> rate >> v; add_edge(dic[u], dic[v], rate); } cout << "Case " << ++cnt << ": "; s = 1; if (!BellmanFord()) cout << "Yes" << endl; else cout << "No" << endl; } return 0; }
题解:这题跟之前正反建图的题目差不多。不过这道题卡了cin取消同步。要用scanf,或者读入挂
#include <iostream> #include <cstring> #include <cstdio> #include <algorithm> #include <queue> #include <cmath> #include <vector> #define ll long long #define pii pair<int, int> using namespace std; const int inf = 0x3f3f3f3f; const int maxn = 1e6+10; struct node { int to,next,w; } edge[2][maxn]; int head[2][maxn], cnt[2], vis[maxn]; int dis[2][maxn]; int n, m, s, t; inline ll read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } void init() { for (int i = 0; i < 2; ++i) for (int j = 1; j <= n; ++j) head[i][j] = 0; cnt[0] = cnt[1] = 0; } void add_edge(int u,int v,int w,int num) { edge[num][++cnt[num]].to = v; edge[num][cnt[num]].w = w; edge[num][cnt[num]].next = head[num][u]; head[num][u] = cnt[num]; } void dijkstra(int num) { for (int i = 1; i <= n; ++i) dis[num][i] = inf; for (int i = 1; i <= n; ++i) vis[i] = 0; priority_queue<pii,vector<pii>,greater<pii> > q;//从小到大 dis[num][s] = 0; q.push(make_pair(dis[num][s],s)); //num = 1; while(!q.empty()) { int now = q.top().second; q.pop(); if(vis[now]) continue; vis[now] = 1; for(int i = head[num][now]; i; i = edge[num][i].next) { int v = edge[num][i].to; if (vis[v]) continue; if(dis[num][v] > dis[num][now] + edge[num][i].w) { dis[num][v] = dis[num][now] + edge[num][i].w; q.push(make_pair(dis[num][v],v)); } } } } int main() { int T; T = read(); while (T--) { n = read(), m = read(); init(); for(int i = 1; i <= m; ++i) { int u, v, w; u = read(), v = read(), w = read(); add_edge(u, v, w, 0); add_edge(v, u, w, 1); //反相建边 } s = 1;//s起点 dijkstra(0), dijkstra(1); ll ans = 0; for (int i = 1; i <= n; ++i) { ans += dis[0][i] + dis[1][i]; } printf("%lld\n", ans); } }
L - Subway POJ - 2502
题解:这道题就有的说了。毕竟我是第一次自己建图这样的。说一下建图思路吧。
hash站点,起点和重点。
每一条地铁线里的站前后建边,权值为欧几里得距离 除以 40km/h。
所有的点之间相互建边,表示人步行的方案,权值为欧几里得距离 除以 10km/h。
剩下的事儿就交给最短路吧。
#include <iostream> #include <cstring> #include <cstdio> #include <algorithm> #include <math.h> #include <queue> #include <iomanip> #include <vector> #define ll long long #define pii pair<double, int> const int inf = 0x3f3f3f3f; const int mod = 1e9+7; const int maxn = 2e5+7; using namespace std; struct node {int to,next;double w;} edge[maxn]; struct point{double x, y;} poi[maxn]; int head[maxn], tot; double dis[maxn]; bool vis[maxn]; int n, m, s, t; inline ll read() { ll x = 0, f = 1; char ch = getchar(); while (ch<'0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while ( ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); } return x * f; } void init() { memset(head,-1,sizeof(head)); for (int i = 0; i < maxn; ++ i) dis[i] = (double)(1<<30); memset(vis,0,sizeof(vis)); tot = 0; } void add_edge(int u,int v, double w) { edge[tot].to = v; edge[tot].w = w; edge[tot].next = head[u]; head[u] = tot ++; } double cal(int a, int b) { //cout << sqrt(((poi[a].x-poi[b].x)*(poi[a].x-poi[b].x) + 1.0*(poi[a].y-poi[b].y)*(poi[a].y-poi[b].y))) << endl; return sqrt(((poi[a].x-poi[b].x)*(poi[a].x-poi[b].x) + 1.0*(poi[a].y-poi[b].y)*(poi[a].y-poi[b].y))); } void dijkstra(int s) { priority_queue<pii,vector<pii>,greater<pii> > q;//从小到大 dis[s] = 0; q.push({dis[s],s}); while(!q.empty()) { int now = q.top().second; q.pop(); if(vis[now]) continue; vis[now] = 1; for(int i = head[now]; i != -1; i = edge[i].next) { int v = edge[i].to; if(dis[v] > dis[now] + edge[i].w) { dis[v] = dis[now] + edge[i].w; q.push({dis[v],v}); } } } } int main() { scanf("%lf%lf%lf%lf",&poi[1].x,&poi[1].y,&poi[2].x,&poi[2].y); init(); double x, y; int cnt = 2; while (~scanf("%lf%lf", &x, &y)) { poi[++ cnt].x = x, poi[cnt].y = y; while (scanf("%lf%lf", &x, &y)) { if (x == -1 && y == -1) break; poi[++ cnt].x = x, poi[cnt].y = y; add_edge(cnt-1, cnt, cal(cnt-1, cnt)/40000), add_edge(cnt, cnt-1, cal(cnt-1, cnt)/40000); } } for (int i = 1; i <= cnt; ++ i) { for (int j = i + 1; j <= cnt; ++ j) add_edge(i, j, cal(i, j)/10000), add_edge(j, i, cal(j, i)/10000); } dijkstra(1); cout << fixed << setprecision(0) << (dis[2]*60.0) << endl; //printf("%.0lf\n", dis[2]*60.0); return 0; }
题解:这道题的限制挺有趣的。建边倒也是非常的明显。
因为限制了交易等级那么,我们最终又一定是要跟酋长交易的,假设酋长的等级为k,题目限制登记为x
那么我们就可以枚举区间[k-x, k], [k-x+1, k+1] ......[k, k+x],
如果枚举人的等级不在这个范围内那么就vis标记为1,表示他不会进入最短路。
不要忘了每次枚举都要初始化dis还有vis,
初始化dis的原因是因为下一次枚举可能会调用上次在区间里而这次不在区间里的人的dis
#include <iostream> #include <cstring> #include <cstdio> #include <algorithm> #include <queue> #include <vector> #define ll long long #define pii pair<int, int> const int inf = 0x3f3f3f3f; const int mod = 1e9+7; const int maxn = 2e4+7; using namespace std; struct node {int to,w,next;} edge[maxn]; int head[maxn], cnt; int dis[maxn], vis[maxn]; inline ll read() { ll x = 0, f = 1; char ch = getchar(); while (ch<'0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while ( ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); } return x * f; } void init() { memset(head,-1,sizeof(head)); memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); cnt = 0; } void add_edge(int u,int v,int w) { edge[cnt].to = v; edge[cnt].w = w; edge[cnt].next = head[u]; head[u] = cnt ++; } void dijkstra(int s) { priority_queue<pii,vector<pii>,greater<pii> > q;//从小到大 dis[s] = 0; q.push({dis[s],s}); while(!q.empty()) { int now = q.top().second; q.pop(); if(vis[now]) continue; vis[now] = 1; for(int i = head[now]; i != -1; i = edge[i].next) { int v = edge[i].to; if(dis[v] > dis[now] + edge[i].w) { dis[v] = dis[now] + edge[i].w; q.push({dis[v],v}); } } } } int main() { int n, x, m; while (~scanf("%d%d",&m,&n)) { init(); int p, l, x; int t, v; int rk[maxn] = {0}; for(int i = 1; i <= n; i ++) { scanf("%d %d %d", &p, &l, &x); add_edge(0, i, p); rk[i] = l; while (x--) { scanf("%d %d", &t, &v); add_edge(t, i, v); } } int temp = inf; int minprice = inf; int maxlv; for(int i = 0; i <= m; i++) { memset(dis,0x3f,sizeof(dis)); memset(vis, 0, sizeof(vis)); maxlv = rk[i]; //把当前物品的等级暂时看做最高等级 for(int j = 1; j <= n; j++) if(rk[1]-m+i>rk[j]||rk[1]+i<rk[j]) vis[j] = 1; dijkstra(0); minprice = min(minprice, dis[1]); //维护最小值 } cout << minprice << endl; } return 0; }
N - Tram POJ - 1847
题解:这题没啥好讲的,就是把边权赋值为1,跑一边 dij 就好了。
#include <iostream> #include <cstring> #include <cstdio> #include <algorithm> #include <queue> #include <cmath> #include <string> #include <vector> #define ll long long #define pii pair<int, int> const int inf = 0x3f3f3f3f; const int mod = 1e9+7; const int maxn = 2e5+7; using namespace std; struct node {int to,next;int w;} edge[maxn]; int head[maxn], cnt, vis[maxn];; int dis[maxn], dis_to[maxn]; int n, s, t; int input() { char ch; ch=getchar(); while( (ch<'0'||ch>'9')&&ch!='x' )ch=getchar(); if(ch=='x')return inf; int ret=0; while(ch>='0'&&ch<='9') { ret*=10; ret+=ch-'0'; ch=getchar(); } return ret; } void init() { memset(head,-1,sizeof(head)); memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); cnt = 0; } void add_edge(int u,int v,int w) { edge[cnt].to = v; edge[cnt].w = w; edge[cnt].next = head[u]; head[u] = cnt ++; } void dijkstra() { priority_queue<pii,vector<pii>,greater<pii> > q;//从小到大 dis[s] = 0; q.push({dis[s],s}); while(!q.empty()) { int now = q.top().second; q.pop(); if(vis[now]) continue; vis[now] = 1; for(int i = head[now]; i != -1; i = edge[i].next) { int v = edge[i].to; if(dis[v] > dis[now] + edge[i].w) { dis[v] = dis[now] + edge[i].w; q.push({dis[v],v}); } } } } int main() { n = input(), s = input(), t = input(); init(); for(int i = 1; i <= n; ++ i) { int m = input(); for (int j = 0; j < m; ++ j) { int temp = input(); add_edge(i, temp, j != 0); } } dijkstra(); cout << ((dis[t] == inf)?(-1):(dis[t])) << endl; return 0; }
O - Extended Traffic LightOJ - 1074
题解:这题挺有趣的。我以为负权环是不能求最短路的。但是其实没被负权环影响的边还是可以求出最短路的。
同时学了如何处理被负权环影响的边。挺好的一道题。
#include <iostream> #include <cstring> #include <cstdio> #include <algorithm> #include <math.h> #include <queue> #include <iomanip> #include <vector> #define ll long long #define pii pair<int, int> const int inf = 0x3f3f3f3f; const int mod = 1e9+7; const int maxn = 2e5+7; using namespace std; struct node {int to,nex,w;} edge[maxn]; int head[maxn], tot; int dis[maxn]; bool vis[maxn], cir[maxn]; int cnt[maxn]; int n, m; inline ll read() { ll x = 0, f = 1; char ch = getchar(); while (ch<'0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while ( ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); } return x * f; } void init() { memset(head, -1, sizeof(head)); memset(dis, inf, sizeof(dis)); memset(vis, 0, sizeof(vis)); memset(cir, 0, sizeof(cir)); memset(cnt, 0, sizeof(cnt)); tot = 0; } void add_edge(int u,int v, int w) { edge[tot].to = v; edge[tot].w = w; edge[tot].nex = head[u]; head[u] = tot ++; } void dfs(int u) { cir[u] = true; //cir用于标记是否被负环影响 for(int i = head[u]; ~i; i = edge[i].nex) { int v = edge[i].to; if (!cir[v]) dfs(v); } } void bfs_spfa(int s) { queue<int> q; q.push(s); dis[s] = 0; vis[s] = 1; //cnt[s] = 1; while (!q.empty()) { int u = q.front(); q.pop(); vis[u] = 0; for(int i = head[u]; ~i; i = edge[i].nex){ int v = edge[i].to, w = edge[i].w; if(cir[v]) continue; //cir用于标记是否被负环影响 //cout << u << "---" << v << " " << " dis[u]:" << dis[u] << " w" << w << " dis[v]:" << dis[v] << endl; if(dis[v] > dis[u] + w) { dis[v] = dis[u] + w; if (!vis[v]) { q.push(v); vis[v] = 1; //接下来和求单源最短路的不同的地方 if(++ cnt[v] > n) dfs(v); //关键不能是大于,是大于等于 } } } } } int main() { int T = read(); for (int Ti = 1; Ti <= T; ++ Ti) { init(); n = read(); int cost[n+1]; for (int i = 1; i <= n; ++ i) cost[i] = read(); m = read(); for (int i = 1; i <= m; ++ i) { int u = read(), v = read(); add_edge(u, v, (cost[v]-cost[u])*(cost[v]-cost[u])*(cost[v]-cost[u])); //cout <<(cost[v]-cost[u])*(cost[v]-cost[u])*(cost[v]-cost[u]) << endl; } bfs_spfa(1); int q = read(); cout << "Case " << Ti << ":" << endl; for (int i = 0; i < q; ++ i) { int t = read(); //cout << dis[t] << endl; if (dis[t] == inf || dis[t] < 3 || cir[t]) cout << "?" << endl; else cout << dis[t] << endl; } } return 0; }
P - The Shortest Path in Nya Graph HDU - 4725
题解:这道题重要的就是建图了。我一开始就是打算点与点之间连,
每个层建一个虚点,之后虚点之间连边,边权为c,每层的点与所属层的虚点连边,边权为0。
之后这些边都是双向边。但是这样就导致了同层点相互访问的话,cost就会是0了(通过虚点过去回来),显然是不行的
只得去看了一眼题解。大致的思路跟我差不多,也是每层弄出一个虚点,虚点与每层的点相连,但是题解是连的是单向边,边权为0
然后前后虚点之间连一条双向边,边权为c,注意这里如果层数不是连续的话是不连的
针对每个点,将他与他所属层数上下两层的虚点相连。
之后跑遍最短路就好了。
#include <iostream> #include <cstring> #include <cstdio> #include <algorithm> #include <queue> #include <cmath> #include <map> #include <vector> #define ll long long #define pii pair<int, int> using namespace std; const int inf = 0x3f3f3f3f; const int maxn = 1e6+100; struct node { int to,next,w; } edge[maxn]; int head[maxn], tot, vis[maxn]; int dis[maxn]; int n, m, c; int layer[maxn]; bool mark[maxn]; inline ll read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } void init() { memset(layer,0,sizeof(layer)); memset(mark,0,sizeof(mark)); memset(head,0,sizeof(head)); memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); tot = 0; } void add_edge(int u,int v,int w) { edge[++tot].to = v; edge[tot].w = w; edge[tot].next = head[u]; head[u] = tot; } void dijkstra(int s) { priority_queue<pii,vector<pii>,greater<pii> > q; dis[s] = 0, q.push({dis[s],s}); while(!q.empty()) { int u = q.top().second; q.pop(); if(vis[u]) continue; vis[u] = 1; for(int i = head[u]; i; i = edge[i].next) { int v = edge[i].to, w = edge[i].w; if (vis[v]) continue; if(dis[v] > dis[u] + w) { dis[v] = dis[u] + w; q.push({dis[v],v}); } } } } int main() { // freopen("in.txt","r",stdin); //输入重定向,输入数据将从in.txt文件中读取 // freopen("out1.txt","w",stdout); //输出重定向,输出数据将保存在out.txt文件中 int T; T = read(); for (int Ti = 1; Ti <= T; ++ Ti) { n = read(), m = read(), c = read(); // if(n<=1) { // printf("Case #%d: 0\n", Ti); // continue; // } init(); for (int i = 1; i <= n; ++ i) mark[layer[i] = read()] = 1; for(int i = 1; i <= m; ++i) { int u, v, w; u = read(), v = read(), w = read(); add_edge(u, v, w), add_edge(v, u, w); } for (int i = 1; i < n; ++ i) { if (mark[i] && mark[i+1]) add_edge(n + i, n + i + 1, c), add_edge(n + i + 1, n + i, c); } for (int i = 1; i <= n; ++ i) { add_edge(layer[i]+n, i, 0); if (layer[i] > 1) add_edge(i, layer[i]-1+n, c); if (layer[i] < n) add_edge(i, layer[i]+1+n, c); } dijkstra(1); printf("Case #%d: %d\n", Ti, dis[n]>=inf?-1:dis[n]); } return 0; }
题解:这道题我根本就没看出来是最短路。思维转换真的很重要。
题目给的三个条件就是说。
一、点1的度为1
二、点n的度为1
三、1-n之间的点度相同
之后求最短路即可。
但是要注意满足上述三种条件的答案有两种情况。
第一种是一条链,第二种是1和n为起点的两个自环。
顺便学习了一下spfa求环花费。
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<map> #include<cstdio> #include<queue> #include<stack> using namespace std; const int INF = 0x3f3f3f3f; int cost[305][305]; int dis[305]; int n; bool vis[305]; void spfa( int start ){ stack<int> Q; for( int i = 1; i <= n; i++ ){ dis[i] = cost[start][i]; if( i != start ){ Q.push( i ); vis[i] = true; } else{ vis[i] = false; } } dis[start] = INF; while( !Q.empty() ){ int x = Q.top(); Q.pop(); vis[x] = false; for( int y = 1; y <= n; y++ ){ if( x == y ) continue; if( dis[x] + cost[x][y] < dis[y] ){ dis[y] = dis[x] + cost[x][y]; if( !vis[y] ){ vis[y] = true; Q.push( y ); } } } } } int main(){ ios::sync_with_stdio( false ); while( cin >> n ){ for( int i = 1; i <= n; i++ ){ for( int j = 1; j <= n; j++ ){ cin >> cost[i][j]; } } int ans, c1, cn; spfa( 1 ); ans = dis[n]; c1 = dis[1]; spfa( n ); cn = dis[n]; cout << min( ans, c1 + cn ) << endl; } return 0; }
20.08.23 UPD:
突然忘了还有几道题目没有补。正巧把最大流学完了。
Q - Marriage Match IV HDU - 3416
这道题就是问你从S到T有几条最短路。首先的思路就是正反跑一遍最短路,因为要确认哪条边在最短路上。
确认方法:S->u + u->v + v->T == minn
之后直接建边跑最大流就好了。
这里千万要注意的是,这里题目给出的虽然是无向图,但是你却要按照有向图正反建。(就是像我代码里,需要分两个vector来存边。
不然会wa
#pragma GCC optimize(2) #include <bits/stdc++.h> using namespace std; #define pii pair<int, int> typedef long long ll; const int maxn = 2e3 + 10; const int maxm = 2e6 + 10; const int inf = 0x3f3f3f3f; const int mod = 1e9 + 7; struct Edge{ int to; int next; int w; } edge[maxm]; int head[maxn], node_num; int cur[maxn];//当前弧优化数组 int n, m, S, T, k;//点数,边数,源点,汇点 int dis[maxn];//Bfs深度 int cnt = 0;//边数 int vis[maxn]; vector<pii> G[maxn][2]; int dist[maxn][2]; inline int read() { int x = 0, f = 1; char ch = getchar(); while (ch<'0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while ( ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); } return x * f; } inline void init(){ cnt = 0; for (int i = 0; i <= n;++ i) head[i] = -1, G[i][0].clear(), G[i][1].clear(); } inline void Add_Edge(int u, int v, int w){ edge[cnt].next = head[u]; edge[cnt].to = v; edge[cnt].w = w; head[u] = cnt++; edge[cnt].next = head[v]; edge[cnt].to = u; edge[cnt].w = 0; head[v] = cnt++; } inline bool Bfs(){ for(int i = 0; i <= n; ++i) dis[i] = -1; //这里要根据你所建的点数来确定 dis[S] = 0; queue<int> q; q.push(S); while(!q.empty()){ int u = q.front(); q.pop(); for(int i = head[u]; ~i ; i = edge[i].next){ int v = edge[i].to; if(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量 dis[v] = dis[u] + 1; q.push(v); } } } return dis[T] != -1; } inline int dfs(int u, int flow) { if(u == T) return flow; int del = flow; for(int i = cur[u]; ~i; i = edge[i].next){ cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间 int v = edge[i].to; if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0 int ans = dfs(v, min(del, edge[i].w));//木桶原理 edge[i].w -= ans;//正向弧减增广流量 edge[i ^ 1].w += ans;//反向弧加增广流量 del -= ans;//总流量减增广流量 if(del == 0) break;//总流量为0则不继续增广 } } return flow - del;//返回本次增广的流量 } inline int Dinic() { int ans = 0; while(Bfs()) { for(int i = 0; i <= n; ++i) cur[i] = head[i]; ans += dfs(S, inf); } return ans; } void dijkstra(int s, int pos) { for (int i = 0; i <= n; ++i) vis[i] = 0, dist[i][pos] = inf; priority_queue<pii,vector<pii>,greater<pii> > q;//从小到大 dist[s][pos] = 0; q.push({dist[s][pos],s}); while(!q.empty()) { int now = q.top().second; q.pop(); if(vis[now]) continue; vis[now] = 1; for(int i = 0; i < G[now][pos].size(); i++) { int v = G[now][pos][i].first; int w = G[now][pos][i].second; if(dist[v][pos] > dist[now][pos] + w) { dist[v][pos] = dist[now][pos] + w; q.push({dist[v][pos],v}); } } } } int main() { int Ti = read(); while (Ti--) { n = read(), m = read(); init(); int px[m+1], py[m+1], pw[m+1]; for (int i = 1,u,v,w; i <= m; ++ i) { u = read(), v = read(), w = read(); px[i] = u, py[i] = v, pw[i] = w; G[u][0].push_back({v,w}), G[v][1].push_back({u,w}); } S = read(), T = read(); dijkstra(S, 0), dijkstra(T, 1); int minn = dist[S][1]; for (int i = 1; i <= m; ++ i) { int u = px[i], v = py[i], w = pw[i]; if (dist[u][0] + w + dist[v][1] == minn) Add_Edge(u, v, 1); } cout << Dinic() << endl; } return 0; }