之前的专题一和二是搜索,特别是广搜尤其多。而该专题最短路算法又可以写成广搜形式,真的快写吐了。。
基本的d最短路算法都基本掌握了,不管什么形式的都能较快的写出来,不过听学弟说什么spfa的slf和lll优化倒是没见过,希望以后能学习到吧。所以如何建图才是解决问题的关键。
C - Heavy Transportation
找能到终点的所有路径的最大值。
需要注意,因为要找最大值,那么优先队列先出来的应该是d大的。
#include<iostream>
#include<stdio.h>
#include<queue>
#include<vector>
#include<cstring>
#include<math.h>
using namespace std;
const int maxn = 1005;
const int inf = 0x3f3f3f3f;
struct Edge
{
int from, to, dist;
Edge(int _f, int _t, int _d) :from(_f), to(_t), dist(_d) {}
};
struct HeapNode
{
int d, u;
bool operator<(const HeapNode &b)const
{
return d < b.d;
}
};
struct Dijkstra
{
vector<int>G[maxn];
vector<Edge>edges;
bool done[maxn];
int d[maxn];
int p[maxn]; //father
int n;
void init(int n)
{
this->n = n;
for (int i = 0;i < n;i++)
G[i].clear();
edges.clear();
//memset(done, 0, sizeof(done));
memset(d, -1, sizeof(d));
}
void Addedge(int u, int v, int dis)
{
edges.push_back(Edge(u, v, dis));
int Size = edges.size();
G[u].push_back(Size - 1);
}
void dijkstra(int s)
{
priority_queue<HeapNode>Q;
memset(d, -1, sizeof(d));
memset(done, 0, sizeof(done));
for (int i = 0;i < G[s].size();i++)
{
Edge &e = edges[G[s][i]];
d[e.to] = max(d[e.to],e.dist);
Q.push(HeapNode{ d[e.to],e.to });
}
done[s] = 1;
d[s] = 0;
while (!Q.empty())
{
HeapNode x = Q.top();Q.pop();
int u = x.u;
if (done[u])continue;
done[u] = 1;
for (int i = 0;i < G[u].size();i++)
{
Edge &e = edges[G[u][i]];
if (d[e.to] < min(d[u], e.dist))
{
d[e.to] = min(d[u], e.dist);
Q.push(HeapNode{ d[e.to],e.to });
}
}
}
}
}dij;
int main()
{
int T;
int n,m;
int cas = 1;
scanf("%d", &T);
while (T--)
{
scanf("%d %d", &n, &m);
dij.init(n);
int u, v, dist;
for (int i = 1;i <= m;i++)
{
scanf("%d %d %d", &u, &v, &dist);
u--;v--;
dij.Addedge(u, v, dist);
dij.Addedge(v, u, dist);
}
dij.dijkstra(0);
printf("Scenario #%d:\n%d\n\n", cas++, dij.d[n-1]);
}
}
F - Wormholes
思路:一开始做最短路,不知道这个要怎么做,看了题解知道了。依照题意建个图,然后看有没有负环,如果有负环则说明他能回到过去。
#include<iostream>
#include<cstring>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxm = 5500;
const int maxn = 505;
const int inf = 0x3f3f3f3f;
int N, M, W;
struct Edge
{
int from, to, dis;
Edge(int _f, int _t, int _d) :from(_f), to(_t), dis(_d){}
Edge(){}
}edges[maxm];
int tot;
void addedge(int u, int v, int dist)
{
edges[tot].from = u;
edges[tot].to = v;
edges[tot].dis = dist;
tot++;
}
int d[maxn];
bool solve()
{
memset(d, inf, sizeof(d));
d[1] = 0;
for (int i = 1;i < N;i++)
for (int j = 0;j < tot;j++)
d[edges[j].to] = min(d[edges[j].to], d[edges[j].from] + edges[j].dis);
for (int i = 0;i < tot;i++)
if (d[edges[i].to] > d[edges[i].from] + edges[i].dis)
return true;
return false;
}
int main()
{
int F;
scanf("%d", &F);
while (F--)
{
tot = 0;
scanf("%d %d %d", &N, &M, &W);
int S, E, T;
for (int i = 1;i <= M;i++)
{
scanf("%d %d %d", &S, &E, &T);
addedge(S, E, T);
addedge(E, S, T);
}
for (int i = 1;i <= W;i++)
{
scanf("%d %d %d", &S, &E, &T);
addedge(S, E, -T);
}
if (solve())
puts("YES");
else
puts("NO");
}
return 0;
}
M - 昂贵的聘礼
思路:一开始想到一个很巧妙的思路,先建一个虚点,然后这个点到其他物品的距离就是物品的价值,物品之间的价值就由题目那样加。然后在最短路算法中维护每个node的等级区间。
结果当然是wa了,仔细想想还是有点问题的,因为更新的d值不知道是在什么情况下更新的,所以不能共用一个d值。
所以这题正确做法是先枚举最低等级,然后在这个这个区间选物品即可。
#include<iostream>
#include<queue>
#include<vector>
#include<cstring>
#include<stdio.h>
#include<algorithm>
#include<math.h>
using namespace std;
const int maxn = 105;
const int maxm = 15000;
const int inf = 0x3f3f3f3f;
#pragma GCC optimize(2)
int M, N;
struct Edge
{
int from, to;
int dist;
int Next;
Edge(int _f,int _t,double _d,int _n):from(_f),to(_t),dist(_d),Next(_n){}
Edge(){}
}edges[maxm];
struct thing
{
int cost, level;
}things[maxn];
vector<int>Vec[maxn],v1;
int Cost[maxn][maxn];
struct HeapNode
{
int d;
int u;
HeapNode(int _d,int _u):d(_d),u(_u){}
HeapNode(){}
bool operator<(const HeapNode &b)const
{
return d > b.d;
}
};
struct Dijkstra
{
int head[maxn];
bool done[maxn];
int d[maxn];
int n;
int tot;
int Right,Left;
void init(int n)
{
this->n = n;
memset(head, -1, sizeof(head));
tot = 0;
}
void addedge(int u, int v, int dist)
{
edges[tot] = Edge(u, v, dist,head[u]);
//edges[tot].Next = head[u];
head[u] = tot++;
}
void dijkstra(int s)
{
memset(done, 0, sizeof(done));
for (int i = 0;i <= n;i++)d[i] = inf;
d[s] = 0;
//Right = things[s].level + M;
priority_queue<HeapNode>Q;
Q.push(HeapNode(d[s], s));
while (!Q.empty())
{
HeapNode x = Q.top();Q.pop();
int u = x.u;
if (done[u])continue;
done[u] = 1;
for (int i = head[u];~i;i=edges[i].Next)
{
Edge &e = edges[i];
if (things[e.to].level > Right||things[e.to].level<Left)continue;
if (d[e.to] > d[e.from] + e.dist)
{
d[e.to] = d[e.from] + e.dist;
Q.push(HeapNode(d[e.to], e.to));
}
}
}
}
}dij;
int main()
{
scanf("%d %d", &M, &N);
int P, L, X,T,V;
for (int i = 1;i <= N;i++)
{
scanf("%d %d %d", &P, &L, &X);
things[i].cost = P;
things[i].level = L;
v1.push_back(L);
for (int j = 1;j <= X;j++)
{
scanf("%d %d", &T, &V);
Vec[i].push_back(T);
Cost[i][T] = V;
}
}
dij.init(N);
for (int i = 1;i <= N;i++)
{
for (int j = 0;j < Vec[i].size();j++)
{
int to = Vec[i][j];
int thecost = Cost[i][to];
dij.addedge(to, i, thecost);
}
}
sort(v1.begin(), v1.end());
unique(v1.begin(), v1.end());
int mincost = inf;
for(int j=0;j<v1.size();j++)
for (int i = 1;i <= N;i++)
{
dij.Right = v1[j] + M;
dij.Left = v1[j];
if (things[i].level<dij.Left || things[i].level>dij.Right)continue;
dij.dijkstra(i);
mincost = min(mincost, dij.d[1] + things[i].cost);
}
cout << mincost << endl;
O - Extended Traffic
思路:一开始没想到还有负值。直接用dijkstra跑了两发。。真没想到amount竟然还有负值。。另外注意格式问题,并没有空行。。
#include<iostream>
#include<cstring>
#include<stdio.h>
#include<queue>
#include<algorithm>
using namespace std;
const int maxn = 205;
const int inf = 0x3f3f3f3f;
int score[maxn];
int map[maxn][maxn];
int d[maxn];
bool inque[maxn];
int cnt[maxn];
int N, M;
int dis(int a, int b)
{
int temp = score[b] - score[a];
return temp*temp*temp;
}
void spfa()
{
queue<int>Q;
Q.push(1);
d[1] = 0;
while (!Q.empty())
{
int x = Q.front();Q.pop();
inque[x] = 0;
for (int i = 1;i <= N;i++)if (map[x][i] != inf)
{
if (d[i] > d[x] + map[x][i])
{
d[i] = d[x] + map[x][i];
if (!inque[i])
{
inque[i] = 1;
if (cnt[i] <= N)
Q.push(i), cnt[i]++;;
}
}
}
}
}
int main()
{
int T;
scanf("%d", &T);
for (int time = 1;time <= T;time++)
{
//printf("\n");
memset(map, inf, sizeof(map));
memset(d, inf, sizeof(d));
memset(inque, 0, sizeof(inque));
memset(cnt, 0, sizeof(cnt));
scanf("%d", &N);
for (int i = 1;i <= N;i++)
{
scanf("%d", &score[i]);
}
int u, v;
scanf("%d", &M);
for (int i = 1;i <= M;i++)
{
scanf("%d %d", &u, &v);
map[u][v] = dis(u, v);
}
spfa();
int q;
printf("Case %d:\n", time);
scanf("%d", &q);
while (q--)
{
scanf("%d", &u);
if (d[u] == inf || d[u]<3)puts("?");
else
printf("%d\n", d[u]);
}
}
return 0;
}
接下来的题目是本专题的精华题!!
P - The Shortest Path in Nya Graph
先看最开始的做法,一开始没想到把layer也当做一个点,结果愚蠢的只要两点有关系就相连,结果re了若干发。。
之后知道了要加虚点,于是把图建成了这样。
之后才发现事情的严重性。。这样建之后同层点的距离变成了0。。
所以正确建图如下(学习学习。。)
代码如下:
#include<iostream>
#include<queue>
#include<vector>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn = 2e5 + 5;
const int maxm = 6e7 + 5;
const int inf = 0x3f3f3f3f;
int idx[maxn];
struct Edge
{
int from, to, dist, next;
Edge(int _f, int _t, int _d, int _next) :from(_f), to(_t), dist(_d), next(_next) {}
Edge() {}
}edges[maxm];
struct HeapNode
{
int d, u;
HeapNode(int _d, int _u) :d(_d), u(_u) {}
HeapNode() {}
bool operator<(const HeapNode &b)const
{
return d > b.d;
}
};
struct Dijkstra
{
vector<int>G[maxn];
int head[maxn];
bool done[maxn];
int d[maxn];
int n;
int tot;
void init(int n)
{
this->n = n;
for (int i = 1;i <= n;i++)
G[i].clear();
memset(head, -1, sizeof(head));
tot = 0;
}
void addedge(int u, int v, int dist)
{
edges[tot] = Edge(u, v, dist, head[u]);
head[u] = tot++;
}
void dijkstra(int s)
{
memset(done, 0, sizeof(done));
memset(d, inf, sizeof(d));
d[s] = 0;
priority_queue<HeapNode>Q;
Q.push(HeapNode(d[s], s));
while (!Q.empty())
{
HeapNode x = Q.top();Q.pop();
if (done[x.u])continue;
done[x.u] = 1;
for (int i = head[x.u];~i;i = edges[i].next)
{
Edge &e = edges[i];
if (d[e.to] > d[x.u] + e.dist)
{
d[e.to] = d[x.u] + e.dist;
Q.push(HeapNode(d[e.to], e.to));
}
}
}
}
}dij;
bool layer[maxn];
int main()
{
int T;
scanf("%d", &T);
int cas = 1;
while (T--)
{
memset(layer, 0, sizeof(layer));
int N, M, C;
scanf("%d %d %d", &N, &M, &C);
dij.init(2*N);
for (int i = 1;i <= N;i++)
scanf("%d", &idx[i]);
int u, v, w;
for (int i = 1;i <= M;i++)
{
scanf("%d %d %d", &u, &v, &w);
dij.addedge(u, v, w);
dij.addedge(v, u, w);
}
for (int i = 1;i < N;i++)
if (layer[i] && layer[i + 1])
dij.addedge(i + N, i + 1 + N, C), dij.addedge(i + 1 + N, i + 1, C);
for (int i = 1;i <= N;i++)
{
dij.addedge(idx[i] + N, i, 0);
if (idx[i] > 1)
dij.addedge(i, idx[i] + N - 1, C);
if (idx[i] < N)
dij.addedge(i, idx[i] + N + 1, C);
}
dij.dijkstra(1);
printf("Case #%d: %d\n", cas++, dij.d[N] == inf ? -1 : dij.d[N]);
}
return 0;
}
Q - Marriage Match IV
又是一道好题。。
一开始想到一个愚蠢的做法,就是疯狂的跑dij。。然后跑一次就记录边,然后把这些边值改成inf。。结果当然T了。。
看了题解又感叹自己为啥总想不到这么好的想法。。
首先跑两遍dij,记录A到所有点和所有点到B的最小值。然后我们来看这些弧,如果A到该弧的弧顶的距离+弧长+弧尾到B的距离等于最小距离,那么这条弧肯定在最短路中,把它加到网络流中,容量为1,接着跑最大流即可。(是我太菜了。。好题!!!)
#include<iostream>
#include<queue>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxm = 1e5 + 5;
const int maxn = 1005;
const int inf = 0x3f3f3f3f;
struct Edge
{
int from, to, dist, next;
Edge(int _f,int _t,int _d,int _n):from(_f),to(_t),dist(_d),next(_n){}
Edge(){}
}edges[2][maxm];
int head[2][maxn],tot[2];
int d[2][maxn];
bool done[2][maxn];
struct HeapNode
{
int d, u;
HeapNode(int _d,int _u):d(_d),u(_u){}
HeapNode(){}
bool operator<(const HeapNode &b)const
{
return d > b.d;
}
};
void addedge(int u, int v, int dist,int idx)
{
edges[idx][tot[idx]] = Edge(u,v, dist, head[idx][u]);
head[idx][u] = tot[idx]++;
}
void dijkstra(int s, int idx)
{
memset(d[idx], inf, sizeof(d[idx]));
memset(done[idx], 0, sizeof(done[idx]));
priority_queue<HeapNode>Q;
d[idx][s] = 0;
Q.push(HeapNode(d[idx][s], s));
while (!Q.empty())
{
HeapNode x = Q.top();Q.pop();
int u = x.u;
if (done[idx][u])continue;
done[idx][u] = 1;
for (int i = head[idx][u];~i;i = edges[idx][i].next)
{
Edge &e = edges[idx][i];
if (d[idx][e.to] > d[idx][u] + e.dist)
{
d[idx][e.to] = d[idx][u] + e.dist;
Q.push(HeapNode(d[idx][e.to], e.to));
}
}
}
}
struct Edge2
{
int from, to, cap, flow;
Edge2(int u, int v, int c, int f) :from(u), to(v), cap(c), flow(f) {}
Edge2() {}
};
struct Dinic
{
int n, m, s, t;
vector<Edge2>edges;
vector<int>G[maxn + 50];
bool vis[maxn + 50];
int d[maxn + 50];
int cur[maxn + 50];
void AddEdge(int from, int to, int cap)
{
edges.push_back(Edge2(from, to, cap, 0));
edges.push_back(Edge2(to, from, 0, 0));
m = edges.size();
G[from].push_back(m - 2);
G[to].push_back(m - 1);
}
void init(int n)
{
for (int i = 0;i <= n;i++)G[i].clear();
edges.clear();
}
bool BFS()
{
memset(vis, 0, sizeof(vis));
queue<int>Q;
Q.push(s);
d[s] = 0;
vis[s] = 1;
while (!Q.empty())
{
int x = Q.front();Q.pop();
for (int i = 0;i < G[x].size();i++)
{
Edge2&e = edges[G[x][i]];
if (!vis[e.to] && e.cap > e.flow)
{
vis[e.to] = 1;
d[e.to] = d[x] + 1;
Q.push(e.to);
}
}
}
return vis[t];
}
int DFS(int x, int a)
{
if (x == t || a == 0)return a;
int flow = 0, f;
for (int &i = cur[x];i < G[x].size();i++)
{
Edge2&e = edges[G[x][i]];
if (d[x] + 1 == d[e.to] && (f = DFS(e.to, min(a, e.cap - e.flow)))>0)
{
e.flow += f;
edges[G[x][i] ^ 1].flow -= f;
flow += f;
a -= f;
if (a == 0)break;
}
}
return flow;
}
int Maxflow(int s, int t)
{
//cout << edges.size() << endl;
this->s = s;this->t = t;
int flow = 0;
while (BFS())
{
memset(cur, 0, sizeof(cur));
flow += DFS(s, inf);
}
return flow;
}
}dinic;
void init()
{
tot[0] = tot[1] = 0;
memset(head, -1, sizeof(head));
}
int main()
{
int Time;
scanf("%d", &Time);
while (Time--)
{
init();
int n, m;
scanf("%d %d", &n, &m);
dinic.init(n);
int a, b, c;
for (int i = 1;i <= m;i++)
{
scanf("%d %d %d", &a, &b, &c);
if (a == b)continue;
addedge(a, b, c, 0);
addedge(b, a, c, 1);
}
int S, T;
scanf("%d %d", &S, &T);
dijkstra(S, 0);
dijkstra(T, 1);
int themin = d[0][T];
for (int i = 0;i < tot[0];i++)
{
Edge &e = edges[0][i];
if (d[0][e.from] + e.dist + d[1][e.to] == themin)
dinic.AddEdge(e.from, e.to, 1);
}
printf("%d\n",dinic.Maxflow(S, T));
}
}
R - 0 or 1
多校的题不愧是多校的题,就是难。。
解题的关键在于如何看出这个模型的本质。
题目给了3个条件,我们从图论的角度来思考问题,可以得到下面3个结论。
1.X12+X13+…X1n=1 于是1号节点的出度为1
2..X1n+X2n+…Xn-1n=1 于是n号节点的入度为1
3.∑Xki =∑Xij 于是2~n-1号节点的入度必须等于出度。
3个条件等价于一条从1号节点到n号节点的路径,题目要求总代价最小,因此最优解答案就是最短路。
所以,我们直接读入边权,跑1到n的最短路即可。记最短路为path。
然而并没有完。。。
以上情况设为A。
情况B:
从1出发,走一个环(至少经过1个点,即不能是自环),回到1;从n出发,走一个环(同理),回到n。
容易验证,这是符合题目条件的。且A || B为该题要求的充要条件。
由于边权非负,于是两个环对应着两个简单环。
因此我们可以从1出发,找一个最小花费环,记代价为c1,再从n出发,找一个最小花费环,记代价为c2。(只需在最短路算法更新权值时多加一条记录即可:if(i==S) cir=min(cir,dis[u]+g[u][i]))
故最终答案为min(path,c1+c2)
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn = 355;
const int inf = 0x3f3f3f3f;
int cost[maxn][maxn];
bool inq[maxn];
int d[maxn];
int Q[maxn], front,tot;
int n;
void spfa(int s)
{
memset(inq, 0, sizeof(inq));
front=tot = 0;
for (int i = 1;i <= n;i++)
{
if (i == s)
d[i] = inf, inq[i] = false;
else
{
d[i] = cost[s][i];
inq[i] = 1;
Q[tot++] = i;
}
}
while (front!=tot)
{
int u = Q[front++];
inq[u] = 0;
for (int i = 1;i <= n;i++)
{
if (d[i] > d[u] + cost[u][i])
{
d[i] = d[u] + cost[u][i];
if (!inq[i])
{
inq[i] = 1;
Q[tot++] = i;
if (tot >= maxn)tot = 0;
}
}
}
if (front >= maxn)front = 0;
}
}
int main()
{
while (~scanf("%d", &n))
{
for (int i = 1;i <= n;i++)
for (int j = 1;j <= n;j++)
scanf("%d", &cost[i][j]);
spfa(1);
int loop1 = d[1];
int ans = d[n];
spfa(n);
int loop2 = d[n];
ans = min(ans, loop1 + loop2);
printf("%d\n", ans);
}
return 0;
}
S - Layout
第一次碰见差分约束的概念。。一开始看这题不知道如何建图,看了题解才有点明白。
我们可以设j>i。
那么依据题意有
1,d[j]-[i]<=Like[i][j]
2,d[j]-d[i]>=DisLike[i][j]
我们当然得构造成<=的样子,也就是最大可以离多远,而求最短路就能使所有条件都满足。所以把2式转换成d[i]-d[j]<=DisLike[i][j]。然后依据这个建图。
当有负环时,则说明不能满足所有条件,输出-1。
当d[N]=inf时,也就是可以放到最远处,输出-2.
其他情况输出d[N]。else if打成if wa了几发很难受。。
代码如下:
#include<iostream>
#include<cstring>
#include<stdio.h>
#include<queue>
using namespace std;
const int maxm = 2e4 + 5;
const int maxn = 1005;
const int inf = 0x3f3f3f3f;
struct Edge
{
int from, to, dist, next;
Edge(int _f,int _t,int _d,int _n):from(_f),to(_t),dist(_d),next(_n){}
Edge(){}
}edges[maxm];
int tot;
int head[maxn];
void addedge(int u, int v, int dis)
{
edges[tot] = Edge(u, v, dis, head[u]);
head[u] = tot++;
}
int d[maxn];
bool inq[maxn];
int cnt[maxn];
int N, ML, MD;
bool spfa(int s)
{
memset(d, inf, sizeof(d));
memset(cnt, 0, sizeof(cnt));
memset(inq, 0, sizeof(inq));
queue<int>Q;
Q.push(s);
inq[s] = 1;
cnt[s] = 1;
d[s] = 0;
while (!Q.empty())
{
int u = Q.front();Q.pop();
inq[u] = 0;
for (int i = head[u];~i;i = edges[i].next)
{
Edge &e = edges[i];
if (d[e.to] > d[u] + e.dist)
{
d[e.to] = d[u] + e.dist;
if (!inq[e.to])
{
inq[e.to] = 1;
Q.push(e.to);
cnt[e.to]++;
if (cnt[e.to] > N)return 0;
}
}
}
}
return 1;
}
int main()
{
while (~scanf("%d %d %d", &N, &ML, &MD))
{
memset(head, -1, sizeof(head));
tot = 0;
int A, B, D;
for (int i = 1;i <= ML;i++)
{
scanf("%d %d %d", &A, &B, &D);if (A > B)swap(A, B);
addedge(A, B, D);
}
for (int i = 1;i <= MD;i++)
{
scanf("%d %d %d", &A, &B, &D);if (A > B)swap(A, B);
addedge(B, A, -D);
}
if (!spfa(1))
puts("-1");
else if (d[N] == inf)puts("-2");
else
printf("%d\n", d[N]);
}
}
总结:做题最重要的是转换思维。要学会将一道让人不知如何下手的题,转换成已有的模型。多多刷题吧!