1.2.1 分算法之Dijkstra
算法本质是贪心,一些不像图论的可贪心的让求关于单源的解的题也能用哦。不能解决负权边。
Eg1.佳佳的魔法药水
一道看起来和最短路没啥关系的题,但是可以贪心啊,每次找一个值最小但却没有确定最小值的药水,将其标记为最小值,然后枚举能与此药水合成药水的药水,用找到的药水与配对的药水更新合成药水的最小值,和Dij相似。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1010;
const int inf = 0x3f3f3f3f;
int n, p[maxn], ans[maxn], now, mp[maxn][maxn];
bool vis[maxn];
void Dijkstra(){
for(int i = 1; i < n; i++){
int low = inf;
for(int j = 1; j <= n; j++)
if(!vis[j] && p[j] < low){
low = p[j];
now = j;
}
vis[now] = 1;
for(int j = 1; j <= n; j++)
if(vis[j] && mp[now][j]){//Dij要找已松弛的!!!!
if(p[mp[now][j]] > p[now] + p[j]){
ans[mp[now][j]] = ans[now] * ans[j];
p[mp[now][j]] = p[now] + p[j];
}
else if(p[mp[now][j]] == p[now] + p[j])
ans[mp[now][j]] += ans[now] * ans[j];
}
}
}
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; i++){
scanf("%d", &p[i]);//表示初始都是靠买哒
ans[i] = 1;
}
int x, y, z;
while(scanf("%d%d%d", &x, &y, &z) != EOF)
mp[x+1][y+1] = mp[y+1][x+1] = z + 1;
Dijkstra();
printf("%d %d\n", p[1], ans[1]);
return 0;
}
Eg2.板子
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1010;
const int inf = 0x3f3f3f3f;
struct data{
int v, w, nxt;
} edge[maxn*maxn*2];
struct node{
int point, w;
bool operator < (const node& a) const {
return w < a.w;
}
};
int dis[maxn], vis[maxn], st[maxn];
int n, m, tot;
inline void in(int x, int y, int z){
edge[++tot].v = y;
edge[tot].w = z;
edge[tot].nxt = st[x];
st[x] = tot;
}
inline void Dijkstra(){
priority_queue<node> q;
for(int i = 0; i <= n; i++) dis[i] = inf;
q.push((node){1, 0});
dis[1] = 0;
while(!q.empty()){
node now = q.top(); q.pop();
if(vis[now.point]) continue;
vis[now.point] = 1;
for(int i = st[now.point]; i; i = edge[i].nxt){
int to = edge[i].v;
if(dis[to] > dis[now.point] + edge[i].w){
dis[to] = dis[now.point] + edge[i].w;
q.push((node){to, dis[to]});
}
}
}
printf("%d\n", dis[n]);
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 1, x, y, z; i <= m; i++){
scanf("%d%d%d", &x, &y, &z);
in(x, y, z);
in(y, x, z);
}
Dijkstra();
return 0;
}
1.2.2 SPFA
自用最短路板子!!!可求瓶颈路!不太稳啊。。希望不被卡。。
Eg1.营救
瓶颈路!!!就是让路上的最大值尽量小!!!把加号改成max即可!
#include<bits/stdc++.h>
using namespace std;
const int maxn = 100010;
int n, m, s, t, tot;
int d[maxn], st[maxn];
bool vis[maxn];
queue<int> q;
struct node{
int v, w, nxt;
} edge[2*maxn];
inline void in(int x, int y, int z){
edge[++tot].v = y;
edge[tot].w = z;
edge[tot].nxt = st[x];
st[x] = tot;
}
inline void spfa(){
memset(d, 0x3f3f3f3f, sizeof(d));
q.push(s);
vis[s] = 1;
d[s] = 0;
while(!q.empty()){
int now = q.front(); q.pop();
vis[now] = 0;
for(int i = st[now]; i; i = edge[i].nxt){
int to = edge[i].v;
if(max(d[now], edge[i].w) < d[to]){
d[to] = max(d[now], edge[i].w);
vis[to] = 1;
q.push(to);
}
}
}
}
int main(){
scanf("%d%d%d%d", &n, &m, &s, &t);
for(int i = 1, x, y, z; i <= m; i++){
scanf("%d%d%d", &x, &y, &z);
in(x, y, z);
in(y, x, z);
}
spfa();
printf("%d\n", d[t]);
return 0;
}
Eg2.玛丽卡
求出最短路然后枚举最短路上的边一一删除,关键在于记录路径的方式,path[to] = i即可,然后用链表来遍历!
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1010;
const int inf = 0x3f3f3f3f;
int tot, n, m, cnt;
struct node{
int u, v, w, next;
} edge[maxn*maxn];
int st[maxn], d[maxn], fe[maxn*maxn];
bool v[maxn];
void in(int a, int b, int c){
edge[++tot].u = a;
edge[tot].v = b;
edge[tot].w = c;
edge[tot].next = st[a];
st[a] = tot;
}
void init(){
memset(v, 0, sizeof(v));
for(int i = 0; i <= n; i++)
d[i] = inf;
}
void spfa(int begin, int end, int judge){
queue<int> q;
init();
d[begin] = 0;
v[begin] = 1;
q.push(begin);
while(!q.empty()){
int from = q.front(); q.pop();
v[from] = 0;
for(int i = st[from]; i > 0; i = edge[i].next){
int to = edge[i].v;
if(d[to] > d[from] + edge[i].w){
d[to] = d[from] + edge[i].w;
if(judge)
fe[to] = i;
if(!v[to]){
v[to] = 1;
q.push(to);
}
}
}
}
}
int main(){
cin >> n >> m;
for(int j = 1, a, b, c; j <= m; j++){
cin >> a >> b >> c;
in(a, b, c);
in(b, a, c);
}
memset(fe, -1, sizeof(fe));
spfa(n, 1, 1);
int ans = d[1];
//cout << ans << endl;
for(int i = fe[1]; i > 0; i = fe[edge[i].u]){
//cout << edge[i].v << " ";
int t = edge[i].w;
//cout << t << endl;
edge[i].w = inf;
spfa(n, 1, 0);
//cout << d[1] << endl;
edge[i].w = t;
ans = max(ans, d[1]);
}
cout << ans << endl;
return 0;
}
Eg3.小K的农场
差分约束系统,a-b >= c 则建一条b到a权值为c的边,a = b则建权值为0的双向边,然后跑SPFA的**DFS版**判负环,这样效率为O(n)。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
typedef long long Lovelive;
using namespace std;
inline void scan(int &x) {
int f = 1; x = 0;char c = getchar();
while(c > '9' || c < '0') {if(c == '-') f= -1; c = getchar();}
while(c >= '0' && c <= '9') x = x*10 + c - '0', c = getchar();
f*x;
}
const int maxn=2e4+10;
int n, m;
struct Edge {
int v, next, val;
}e[maxn];
int last[maxn], tot;
void add(int u, int v, int w) {
e[++tot].next=last[u]; e[tot].v=v; e[tot].val=w; last[u]=tot;
}
int dis[maxn];
bool vis[maxn];
bool SPFA(int u) {
vis[u]=1;
for(int i=last[u]; i; i=e[i].next) {
if(dis[e[i].v] > dis[u]+e[i].val) {
dis[e[i].v] = dis[u]+e[i].val;
if(vis[e[i].v]) return false;
else if(!SPFA(e[i].v)) return false;
}
}
vis[u]=0;
return true;
}
int main() {
scan(n); scan(m);
while(m--) {
int k; scan(k);
if(k == 3) { int a, b; scan(a), scan(b); add(b, a, 0); add(a, b, 0);}
else {
int a, b, c;
scan(a); scan(b); scan(c);
if(k==1) add(a, b, -c);
else add(b, a, c);
}
}
for(int i=0; i<=n; i++) add(0, i, 0), dis[i]=maxn;
dis[0]=0;
if(SPFA(0)) cout << "Yes";
else cout << "No";
return 0;
}
Eg4.最小圈
分数规划,同样用SPFA判负环,二分答案。
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
const int N = 3005, M = 1e4 + 5; const double eps = 1e-9;
int n, m, ecnt, nxt[M], adj[N], go[M], w[M]; double val[M], dis[N];
bool Flag, vis[N];
void add_edge(int u, int v, int x) {
nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v; w[ecnt] = x;
}
void dfs(int u) {
vis[u] = 1;
for (int e = adj[u], v; e; e = nxt[e])
if (dis[u] + val[e] < dis[v = go[e]]) {
if (vis[v]) return (void) (Flag = 1);
else {
dis[v] = dis[u] + val[e];
dfs(v);
}
}
vis[u] = 0;
}
bool check(double x) {
int i; memset(vis, 0, sizeof(vis));
for (i = 1; i <= ecnt; i++) val[i] = 1.0 * w[i] - x;
memset(dis, 0, sizeof(dis));
for (i = 1; i <= n; i++) {
Flag = 0;
if (dfs(i), Flag) return 1;
}
return 0;
}
double solve() {
double l = -1e5, r = 1e5;
while (fabs(r - l) > eps) {
double mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;
}
return l;
}
int main() {
int i, x, y, z; n = read(); m = read();
for (i = 1; i <= m; i++) x = read(), y = read(),
z = read(), add_edge(x, y, z);
printf("%.8lf\n", solve());
return 0;
}
1.2.3 Floyd
预处理多元会用到
Eg1.Vijos 观光旅游
判最小正环
//设环的起点(终点)为i,那么环可以表示为i->k->j->x1,x2...(不经过k)->i
//环的长度可以表示为i->j经过k的dist+i->不经过k的dist
//i->j经过k的dist普通floyd就能求到,不经过k的dist需要构造
//在求这个环时,保持k是最大值,那么所有x1,x2...都比k要小
//循环遍历至k-1时存储的就是j->x1,x2...->i的最小值
for(int k=1;k<=n;k++){//k是用来更新的中间点,保持k比i,j都要大,
for(int i=1;i<k;i++)//floyd每次更新从i开始到j结束,中间经过k的距离
for(int j=i+1;j<k;j++){
ans=min(ans,dist[i][j]+edge[i][k]+edge[k][j]);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);
}
if(ans<INF) printf("%d\n",ans);