题意:
给出一张含有n个结点、m条边有向图,要求删除边使得 1 -> n 最短路的总长增大(或无法到达),删除的花费即边的长度,问最小花费为多少。
分析:
很容易想到把最短路全部拎出来建个新图,然后用网络流求最小割。(记得注意数据范围)
但是比较麻烦的就是跑完最短路以后怎么把 所有的1 -> n 的最短路 都拎出来。
- 方法一
看到别人的方法是,分别建一个和正向图和一个反向图,正向图跑一遍从 点1 开始的最短路,反向图跑一遍从 点n 开始的最短路,然后对于一条 长度为c 的 边 u -> v,
若 dist[1->u] + dist[n->v] + c == dist[1->n] ,则 该边属于 1-> n 的最短路。
- 方法二
而我的方法是直接在从点1开始的求最短路时,用一个pre[]数组记录所有的最短路(pre[v]内存储以v为结尾的最短路),然后建图时从点n开始递归建图就行。
但是在建图的时候要注意不要重复加边,一个结点v只要访问过,以其结尾的最短路就已经加入新图中了,下一次再访问到v时就直接返回。
具体看代码注释。
以下代码:
#include<bits/stdc++.h>
#define PII pair<int,LL>
#define LL long long
using namespace std;
const LL INF=0x3f3f3f3f;
const int maxn=1e4+50;
const int maxm=4e4+50;
struct edge
{
int u;
int v;
LL c;
int next;
}e[maxm];
int head[maxn],cnt;
void addedge(int u,int v,LL c)
{
e[cnt].u=u;
e[cnt].v=v;
e[cnt].c=c;
e[cnt].next=head[u];
head[u]=cnt++;
}
int n,m;
void init()
{
memset(head,-1,sizeof(head));
cnt=0;
}
struct node
{
int id;
LL d;
friend bool operator < (const node &a, const node &b)
{
return a.d > b.d;
}
node(int a,LL b)
{
id=a;
d=b;
}
};
LL dist[maxn];
bool vis[maxn];
vector<PII> pre[maxn];
void dijkstra(int s)
{
memset(dist,0x3f,sizeof(dist));
memset(vis,false,sizeof(vis));
dist[s]=0;
priority_queue<node> q;
q.push(node(s,0));
while (!q.empty())
{
int u=q.top().id;
q.pop();
if(vis[u])
continue;
else
vis[u] = true;
for (int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].v;
LL c=e[i].c;
if (!vis[v]&&dist[u]+c<dist[v])
{
dist[v]=dist[u]+c;
pre[v].clear(); //最短距离改变,先清空pre,再放入新边
pre[v].push_back(PII(u,c)); //存入前驱结点u和该边长度c
q.push(node(v,dist[v]));
}
else if(dist[u]+c==dist[v]) //最短距离相同,放入该边
pre[v].push_back(PII(u,c));
}
}
}
int dis[maxn];
int s,t;
bool bfs()
{
memset(dis,-1,sizeof(dis));
queue<int> q;
q.push(s);
dis[s]=0;
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].v;
if(e[i].c>0&&dis[v]==-1)
{
dis[v]=dis[u]+1;
if(v==t)
return true;
q.push(v);
}
}
}
return false;
}
int cur[maxn];
LL dfs(int u,LL flow)
{
if(u==t)
return flow;
for(int &i=cur[u];i!=-1;i=e[i].next)
{
int v=e[i].v;
if(dis[v]==dis[u]+1&&e[i].c>0)
{
LL k=dfs(v,min(flow,e[i].c));
if(k>0)
{
e[i].c-=k;
e[i^1].c+=k;
return k;
}
}
}
return 0;
}
LL dinic()
{
LL ans=0;
while(bfs())
{
for(int i=0;i<maxn;i++)
cur[i]=head[i];
while(LL k=dfs(s,INF))
ans+=k;
}
return ans;
}
bool flag[maxn];
void built(int v) //递归建图
{
if(flag[v]) //不要重复访问
return;
else
flag[v]=true;
for(int i=0;i<pre[v].size();i++)
{
int u=pre[v][i].first;
LL c=pre[v][i].second;
addedge(u,v,c); //建边
addedge(v,u,0);
built(u); //递归
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
init();
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++)
{
int x,y;
LL c;
scanf("%d %d %lld",&x,&y,&c);
addedge(x,y,c);
}
dijkstra(1); //最短路
if(dist[n]==INF) //不可达直接输出0
{
printf("0\n");
continue;
}
init(); //记得建图前初始化
memset(flag,0,sizeof(flag));
built(n); //建新图
s=1;
t=n;
printf("%lld\n",dinic()); //最小割
}
return 0;
}