题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6582
来源:2019 Multi-University Training Contest 1
题目大意:
给定一张有向图,可以阻碍若干条有向边,花费为边的权值,求使其最短路变得更长所需的最小花费。
解题思路:
1.因为最短路可能是多条,所以找出最短路网络,然后在最短路网络中跑最小割,即最大流。就切断了原先的最短路且保证了是最小花费(最小割)。
2.值得注意的地方:边的长度限制为1e9,所以最短路数组以及一些其他的求和的值要开long long型,防止爆。
3.求最短路核心边需满足 dis[u] + edge[i].w == dis[v]。那么edge[i].w就是一条最短路的核心边。同时要确保合法性,满足dis[u] != inf && dis[v] != inf
代码如下:
#include<stdio.h> #include<string.h> #include<queue> #define LL long long #define mem(a, b) memset(a, b, sizeof(a)) const int MAXN = 1e4 + 100; const LL inf = 0x3f3f3f3f; using namespace std; int n, m;//n个点 m条有向边 int vis[MAXN];//源点到该点的最短距离是否被确定 LL dis[MAXN]; struct Edge { int to, next; LL w; }e[MAXN]; int head[MAXN], cnt; void add(int a, int b, int c) { e[++ cnt].to = b; e[cnt].w = c; e[cnt].next = head[a]; head[a] = cnt; } struct Node { int pot; LL dis; //点 以及 源点到该点的最短距离 bool operator < (const Node &a)const//重载 按照dis从小到大排序 { return dis > a.dis; } }no; priority_queue<Node> Q; void dij() //优先队列优化的最短路 { mem(vis, 0), mem(dis, inf); no.dis = 0, no.pot = 1; dis[1] = 0; Q.push(no); while(!Q.empty()) { Node a = Q.top();//优先队列无front操作 Q.pop(); if(vis[a.pot]) continue; vis[a.pot] = 1; for(int i = head[a.pot]; i != -1; i = e[i].next)//松弛操作 { int to = e[i].to; if(!vis[to] && dis[a.pot] + e[i].w < dis[to]) { dis[to] = dis[a.pot] + e[i].w; no.pot = to, no.dis = dis[to]; Q.push(no); } } } // printf("%d ", dis[n]); } struct edge_ { int to, next, flow; }e_[2 * MAXN];//要加反向边 开2倍 int head_[MAXN]; void add_(int a, int b, int c) { e_[++ cnt].to = b; e_[cnt].flow = c; e_[cnt].next = head_[a]; head_[a] = cnt; } int dep[MAXN]; int bfs(int st, int ed) { if(st == ed) return 0; mem(dep, -1); queue<int >Q_; dep[st] = 1; Q_.push(st); while(!Q_.empty()) { int now = Q_.front(); Q_.pop(); for(int i = head_[now]; i != -1; i = e_[i].next) { int to = e_[i].to; if(e_[i].flow > 0 && dep[to] == -1) { dep[to] = dep[now] + 1; Q_.push(to); } } } return dep[ed] != -1; } int dfs(int now, int ed, int inc) { if(now == ed) return inc; for(int i = head_[now]; i != -1; i = e_[i].next) { int to = e_[i].to; if(e_[i].flow > 0 && dep[to] == dep[now] + 1) { int min_flow = dfs(to, ed, min(inc, e_[i].flow)); if(min_flow > 0) { e_[i].flow -= min_flow; e_[i ^ 1].flow += min_flow; return min_flow; } } } return -1; } LL dinic(int st, int ed) { LL ans = 0; while(bfs(st, ed)) { while(1) { int inc = dfs(st, ed, inf); if(inc == -1) break; ans += inc; } } return ans; } int main() { int T; scanf("%d", &T); while(T --) { scanf("%d%d", &n, &m); cnt = 0, mem(head, -1); //最短路的边从1开始存 head初始化为-1 for(int i = 1; i <= m; i ++) { int a, b, c; scanf("%d%d%d", &a, &b, &c); add(a, b, c); //有向边 加一条即可 } dij(); //跑最短路 if(dis[n] == inf) //特判 { printf("0 "); continue; } cnt = -1, mem(head_, -1);//从0开始存 才能进行 ^ 运算 for(int i = 1; i <= n; i ++) { for(int j = head[i]; j != -1; j = e[j].next) { int to = e[j].to; if(dis[i] + e[j].w == dis[to] && dis[to] != inf && dis[i] != inf) //得到最短路核心边(因为可能是多条) { add_(i, to, e[j].w); add_(to, i, 0); //反向边容量为 0 } } } printf("%lld ", dinic(1, n)); } return 0; }