题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3416
题意:
每条边最多只走一次,统计最短路径条数。
解题思路:
看到题目,限制了走的次数,想到了网络流,容量为1,即为限制一次,又统计最短路径条数,想到最小费用最大流,在增流时判断现在增流的花费是否大于最短路径,也就是第一次增流的花费,如果大于,结束,如果不大于,继续。结果TLE
看题解后,很强大。
既然只统计最短路径条数,那么只有在最短路径的边是有用的,如果能统计出起始点到所有点的最短距离dist1,所有点到终止点的最短距离dist2,任意一条边,看这条边的起始点dist1加上边权加上终止点的dist2,如果等于最短路径,则在最短路径中,是有效的,如果不满足,则不再最短路径中,也就没有用了。
找到由最短路径构成的图,跑一遍最大流,就得到答案。
完成上述这的过程就需要dist1和dist2,也就是正图以起始点为起始点跑最短路,重新建逆图,逆图以终止点为起始点跑一遍最短路,就得到dist1和dist2.然后重新建图。这个就很强势了,让我知道了链式前向星怎么建逆图。之所以逆图表示所有点到终止点的距离,可以这样理解,逆图中终止点指向的边都是原本指向终止点的,那么,在逆图中跑的最短路,就是其他点跑向终止点的最短路。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
typedef pair<int, int> P;
const int N = 1010;
const int M = 100005;
const int INF = 1e9;
struct edge {
int to, next;
int dist, c, f;
}graph[M<<1];
int totlen;
int head[N];
int n, m;
int a[M], b[M], c[M];
int s, t;
// Dinic
int level[N];
int cur[N];
bool dinic_bfs() {
memset(level, 0, sizeof(level));
queue<int> que;
que.push(s);
level[s] = 1;
while(!que.empty()) {
int u = que.front(); que.pop();
for(int i = head[u]; i != -1; i = graph[i].next) {
int v = graph[i].to;
if(!level[v] && graph[i].c > graph[i].f) {
level[v] = level[u]+1;
que.push(v);
}
}
}
return level[t] != 0;
}
int dinic_dfs(int u, int cpflow) { // cpflow: can pass flow 到达u点最大能通过的流量
if(u == t) return cpflow; // 到达汇点
int addflow = 0; // u 点到其他点 最多能增广的流量, 最多不能超过cpflow,由前面的边限制
for(int& i = cur[u]; i != -1 && addflow < cpflow; i = graph[i].next){
int v = graph[i].to;
if(level[u]+1 == level[v] && graph[i].c > graph[i].f) {
// 这一条路上增广的流量
int tmpadd = dinic_dfs(v, min(cpflow-addflow, graph[i].c-graph[i].f));
if(tmpadd > 0) {
graph[i].f += tmpadd; // 正向通过的流量加
graph[i^1].f -= tmpadd; // 反向的流量就得减
return tmpadd;
}
}
}
return 0; // 返回这个点都汇点能增广的流量
}
int dinic() {
int maxflow = 0;
int tmpflow = 0;
while(dinic_bfs()) {
memcpy(cur, head, sizeof(head));
while((tmpflow = dinic_dfs(s, INF)) > 0)
maxflow += tmpflow;
}
return maxflow;
}
// Dijkstra
bool visit[N];
int dist1[N], dist2[N];
priority_queue<P, vector<P>, greater<P> > que;
void dijkstra(int s, int* dist) {
for(int i = 0; i <= n; i++) dist[i] = INF;
for(int i = 0; i <= n; i++) visit[i] = 0;
while(!que.empty()) que.pop();
dist[s] = 0;
que.push(P(0, s));
while(!que.empty()) {
P cur = que.top(); que.pop();
int u = cur.second;
if(visit[u]) continue;
visit[u] = true;
for(int i = head[u]; i != -1; i = graph[i].next)
if(!visit[graph[i].to]) {
int v = graph[i].to;
if(dist[v] > dist[u]+graph[i].dist) {
dist[v] = dist[u]+graph[i].dist;
que.push(P(dist[v], v));
}
}
}
}
void addEdge(int u, int v, int c, int dist) {
if(u == v) return;
// 正向边
graph[totlen].to = v;
graph[totlen].next = head[u];
graph[totlen].dist = dist;
graph[totlen].c = c;
graph[totlen].f = 0;
head[u] = totlen++;
// 添加反向边
graph[totlen].to = u;
graph[totlen].next = head[v];
graph[totlen].dist = INF;
graph[totlen].c = 0;
graph[totlen].f = 0;
head[v] = totlen++;
// 正向边下标通过异或就得到反向边下标, 2 ^ 1 == 3 ; 3 ^ 1 == 2
}
void init() {
for(int i = 0; i <= n; i++) head[i] = -1;
totlen = 0;
}
int main() {
int _;
scanf("%d", &_);
while(_--) {
scanf("%d%d", &n, &m);
for(int i = 0; i < m; i++){
int u, v, w;
scanf("%d%d%d", &a[i], &b[i], &c[i]);
}
scanf("%d%d", &s, &t);
init();
for(int i = 0; i < m; i++)
addEdge(a[i], b[i], 1, c[i]);
dijkstra(s, dist1);
init();
for(int i = 0; i < m; i++)
addEdge(b[i], a[i], 1, c[i]);
dijkstra(t, dist2);
init();
for(int i = 0; i < m; i++) {
int u = a[i];
int v = b[i];
int w = c[i];
if(dist1[u]+dist2[v]+w == dist1[t]) {
addEdge(u, v, 1, w);
}
}
int ans = dinic();
printf("%d\n", ans);
}
return 0;
}