AcWing 341. 最优贸易
题目:https://www.acwing.com/problem/content/343/
题目意思就是找到一条从1到n的路中的最大权值减去最小权值最大
建图,使用in[i]数组表示从1~i的路中的最小权值
建反向图,在反向图上用out[i]数组表示从n~i的路中的最大权值
最后遍历所有点,找出最大的out[i]-in[i]就是答案
题目:
#include<bits/stdc++.h>
using namespace std;
const int maxn1 = 1e5 + 7;
const int maxn2 = 1e6 + 7;
const int INF = 0x3f3f3f3f;
int in[maxn1], out[maxn1], num[maxn1];
int Head1[maxn1], To1[maxn2], Nxt1[maxn2]; //链式向前星建图
int Head2[maxn1], To2[maxn1], Nxt2[maxn2];
int tot1, tot2;
int n, m;
void add_edge(int fro, int to) {
Nxt1[++tot1] = Head1[fro]; //正向图
To1[tot1] = to;
Head1[fro] = tot1;
Nxt2[++tot2] = Head2[to]; //反向图
To2[tot2] = fro;
Head2[to] = tot2;
}
typedef pair<int, int> P; //边,点
priority_queue<P, vector<P>, greater<P>>Q;
void Dijstra1() { //跑正向图
memset(in, INF, sizeof(in));
while (!Q.empty()) Q.pop();
in[1] = num[1];
Q.push(P(in[1], 1));
while (!Q.empty()) {
P p = Q.top(); Q.pop();
int val = p.first, poi = p.second;
if (in[poi] < val) continue;
for (int i = Head1[poi]; i; i = Nxt1[i]) {
int to = To1[i];
if (in[to] > in[poi]) {
in[to] = min(in[poi], num[to]);
Q.push(P(in[to], to));
}
}
}
}
void Dijstra2() { //跑反向图
memset(out, 0, sizeof(out));
out[n] = num[n];
while (!Q.empty()) Q.pop();
Q.push(P(out[n], n));
while (!Q.empty()) {
P p = Q.top(); Q.pop();
if (out[p.second] > p.first) continue;
for (int i = Head2[p.second]; i; i = Nxt2[i]) {
int to = To2[i];
if (out[to] < out[p.second]) {
out[to] = max(num[to], out[p.second]);
Q.push(P(out[to], to));
}
}
}
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d", num + i);
}
int fro, to, opt;
for (int i = 1; i <= m; i++) {
scanf("%d %d %d", &fro, &to, &opt);
add_edge(fro, to);
if (opt == 2) add_edge(to, fro);
}
Dijstra1();
Dijstra2();
int maxx = 0;
for (int i = 1; i <= n; i++) {
maxx = max(maxx, out[i] - in[i]);
}
printf("%d\n", maxx);
}
bzoj 2200: https://www.lydsy.com/JudgeOnline/problem.php?id=2200
因为这道题有负边的存在,所以不可以用迪杰斯特拉求最短路,而用SPFA的话时间复杂度最差为O(nm),此题假如特殊构造图形则会卡时间,事实上(听说)就会卡SPFA
因为这道题一部分是双向路,一部分是单向路,只有单向路才有负数,可以利用这点解题。
题目还有另外的条件,假如有从a到b的单向路的话,就必然没有从b到a的路,也就是说将a到b的路切开之后跟a,b之间就断开了,所有跟a联通的点和所有跟b联通的点都会分开。
所以可以缩点,也就是说先构建一张只有双向边的图,那么此图一定是分割成不同块的图,将同一块的所有点压缩成一个点,之后再加入单向边。
然后根据欧拉序(每次从入度为0的大点里),在每个大点里跑迪杰斯特拉,这样就没有负边了
#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e4;
const int INF = 0x3f3f3f3f;
struct edge {
int to, val;
};
vector<edge> plan[maxn], road[maxn]; //plan 海路 road 陆路
int f[maxn], belong[maxn], dis[maxn], deg[maxn];
//f 并查集的父亲节点,缩点用, belong[i] : i属于哪个大点 dis:距离数组 deg:入度数组
int vis[maxn];
int T, R, P, S;
int find(int x) { //并查集
return f[x] == x ? x : f[x] = find(f[x]);
}
void combine() { //缩点
for (int i = 1; i <= T; i++) f[i] = i;
for (int i = 1; i <= T; i++) {
for (int j = 0; j < road[i].size(); j++) {
int x = find(i), y = find(road[i][j].to);
if (x != y) f[x] = y;
}
}
}
void get_deg() { //获得每个大点的入度
queue<int> Q;
memset(vis, 0, sizeof(vis));
Q.push(S);
vis[S] = 1;
while (!Q.empty()) {
int now = Q.front(); Q.pop();
for (int i = 0; i < road[now].size(); i++) {
int &to = road[now][i].to;
if (!vis[to]) {
vis[to] = 1;
Q.push(to);
}
}
for (int i = 0; i < plan[now].size(); i++) {
int &to = plan[now][i].to;
++deg[find(to)];
if (!vis[to]) {
vis[to] = 1;
Q.push(to);
}
}
}
}
queue<int> dq;
vector<int> go[maxn]; //每个大点中跑迪杰斯特拉时的起点数组,这几个点时跑dijstra
typedef pair<int, int> PP; //pair放点和边, pp.first: 边 pp.second: 点
void Dijstra(int in) { //in放的一定是这个点的id(大点的id)
priority_queue<PP, vector<PP>, greater<PP> > Q;
for (int i = 0; i < go[in].size(); i++) {
Q.push(PP(dis[go[in][i]], go[in][i]));
}
while (!Q.empty()) {
PP p = Q.top(); Q.pop();
int now = p.second;
if (vis[now]) continue;
vis[now] = 1;
for (int i = 0; i < road[now].size(); i++) {
int to = road[now][i].to, val = road[now][i].val;
if (dis[to] > dis[now] + val) {
dis[to] = dis[now] + val;
Q.push(PP(dis[to], to));
}
}
for (int i = 0; i < plan[now].size(); i++) {
int to = plan[now][i].to, val = plan[now][i].val;
int belong = find(to);
if (dis[to] > dis[now] + val) {
dis[to] = dis[now] + val;
go[belong].push_back(to);
}
deg[belong]--;
if (deg[belong]==0) dq.push(belong);//没入度就可以了
}
}
}
void top_solve() {
dq.push(find(S));
go[find(S)].push_back(S);
memset(vis, 0, sizeof(vis));
while (!dq.empty()) {
int now = dq.front();
dq.pop();
Dijstra(now);
}
}
int main() {
cin >> T >> R >> P >> S;
memset(dis, INF, sizeof(dis));
dis[S] = 0;
for (int i = 1; i <= R; i++) {
int fro, to, val;
scanf("%d %d %d", &fro, &to, &val);
edge e;
e.to = to, e.val = val;
road[fro].push_back(e);
e.to = fro;
road[to].push_back(e);
}
combine(); //缩点
for (int i = 1; i <= P; i++) {
int fro, to, val;
scanf("%d %d %d", &fro, &to, &val);
edge e;
e.to = to, e.val = val;
plan[fro].push_back(e);
}
get_deg();//计算入度
top_solve();
for (int i = 1; i <= T; i++) {
if (dis[i] == INF) printf("NO PATH\n");
else printf("%d\n", dis[i]);
}
return 0;
}
poj 1734 Sightseeing trip
原题:http://poj.org/problem?id=1734
用ori[i][j]存放原始读入的路径长度,用ans保存最小权值的环
使用弗洛伊德的最外层循环k的时候,dis[i][j]表示经过不超过编号为k-1的点从i~j的最短路(i,j也不超过k-1)
对于第k个点,则让ans 和 dis[i][j] + ori[i][k] + ori[k][j] 比较 (将i,j看为回路的一条边,再加上ik以及kj这两条边构成环)
然后再对dis进行第k层的弗洛伊德转化(具体可以看代码)
然后还有一个是记录路径,可以使用一个二维数组bet[i][j]记录弗洛伊德转化的时候作为i,j中继点的k的值
每一次ans更新,都需要从头更新一次路径
注意:代码有一个坑点:
就是假如初始化dis数组为INF,那么在比较dis[i][j] + ori[i][k] + ori[k][j]的时候可能会爆long long
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#define ll long long
#define ull unsigned long long
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 307;
int ori[maxn][maxn], dis[maxn][maxn];
//ori:origin,记录原始道路长 dis:记录弗洛伊德之后的距离
int path[maxn], bet[maxn][maxn];
// path:记录最短路径 bet:between 记录i,j之间用于更新他们最短距离的中继点
int ind = 0;
void get_path(int i,int j) { //dfs更新路径
if (bet[i][j] == 0) return;
get_path(i, bet[i][j]);
path[++ind] = bet[i][j];
get_path(bet[i][j], j);
}
int main() {
int n, m;
cin >> n >> m;
memset(dis, INF, sizeof(dis));
memset(ori, INF, sizeof(ori));
while (m--) {
int fro, to, val;
scanf("%d %d %d", &fro, &to, &val);
dis[fro][to] = dis[to][fro] = val;
ori[fro][to] = ori[to][fro] = val;
}
int ans = INF;
for (int k = 1; k <= n; k++) {
for (int i = 1; i < k; i++) {
for (int j = i + 1; j < k; j++) {
if ((long long)dis[i][j] + ori[k][j] + ori[i][k] < ans ) { // 这里 不等式前面有可能会爆int
ans = dis[i][j] + ori[k][j] + ori[i][k];
ind = 0; //每一次更新答案都需要重置path数组
path[++ind] = i;
get_path(i, j);
path[++ind] = j;
path[++ind] = k;
}
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (dis[i][j] > dis[i][k] + dis[k][j]) { //弗洛伊德更新
dis[i][j] = dis[i][k] + dis[k][j];
bet[i][j] = k;
}
}
}
}
if (ans == INF) cout << "No solution." << endl;
else {
for (int i = 1; i <= ind; i++) printf("%d ", path[i]);
printf("\n");
}
return 0;
}
AcWing 349 黑暗城堡
原题:https://www.acwing.com/problem/content/351/
首先S[i]=D[i] 就表示需要跑一次单源最短路
当方案不同的时候,但是仍然需要S[i]=D[i],其中D[i]是不会变的,因此S[i]不变。
因此需要有dis[j]=dis[i]+edge[i,j] 成立,才能保证s[i]不变。
(我觉得代码很清楚了)
那么dis[j]=dis[i]+edge[i,j]成立的时候,点i更换其连接的边,会不会对其他点有影响呢?
先看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 7;
const int INF = 0x3f3f3f3f;
int Head[maxn], Nxt[maxn << 1], Val[maxn << 1], To[maxn << 1];
int n, m, tot;
void add_edge(int fro, int to, int val) {
Nxt[++tot] = Head[fro];
To[tot] = to;
Val[tot] = val;
Head[fro] = tot;
}
int dis[1007], vis[1007];
typedef pair<int, int> P;
priority_queue < P, vector<P>, greater<P> > Q;
void Dijstra() {
memset(dis, INF, sizeof(dis));
dis[1] = 0;
Q.push(P(0, 1));
while (!Q.empty()) {
P p = Q.top();
Q.pop();
if (vis[p.second]) continue;
int now = p.second;
for (int i = Head[now]; i; i = Nxt[i]) {
int &to = To[i], &val = Val[i];
if (dis[to] > dis[now] + val) {
dis[to] = dis[now] + val;
Q.push(P(dis[to], to));
}
}
}
}
#define ll long long
ll ans = 1;
ll mod = (1 << 31) - 1;
ll sto[1007];
int main() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int fro, to, val;
scanf("%d %d %d", &fro, &to, &val);
add_edge(fro, to, val);
add_edge(to, fro, val);
}
Dijstra();
memset(vis, 0, sizeof(vis));
memset(sto, 0, sizeof(sto));
for (int i = 1; i <= n; i++) {
for (int j = Head[i]; j; j = Nxt[j]) {
int &to = To[j], &val = Val[j];
if (dis[to] == dis[i] + val) {
sto[to]++;
}
}
}
for (int i = 2; i <= n; i++) {
ans *= sto[i];
ans %= mod;
}
cout << ans << endl;
}
比如原图是这个
对于这个图,可以找到一棵树
当寻找到d这个点的时候,dis[b]=dis[d]+edge[b,d]
是不是变成:
那E就被影响了
当然不是,其实应该是这样的:
切断的应该是通往B的,而不是通往D的边
仅仅通过这个例子来说明这样做的正确性