题目背景
SOURCE:NOIP2015-SHY4
题目描述
有一天,TT 要去 ABC 家。ABC 的大门外有 n 个站台,用 1 到 n 的正整数编号,TT 需要对每个站台访问恰好一定次数以后才能到 ABC 家。站台之间有 m 个单向的传送门,通过传送门到达另一个站台不需要花费任何代价。而如果不通过传送门,TT 就需要乘坐公共汽车,并花费 1 单位的钱。值得庆幸的是,任意两个站台之间都有公共汽车直达。
现在给定每个站台必须访问的次数,对于站台 i ,TT 必须恰好访问 Fi 次(不能超过)。
我们用 u,v,w 三个参数描述一个传送门,表示从站台 u 到站台 v 有一个最多可以使用 w 次的传送门(不一定要使用 w 次)。对于任意一对传送门 (u1,v1) 和 (u2,v2),如果有 u1<u2,则有v1≤v2;如果有 v1<v2 ,则有 u1≤u2;且 u1=u2 和 v1=v2 不同时成立。
TT 可以从任意的站台开始,从任意的站台结束。出发去开始的站台需要花费 1 单位的钱。现在请帮助 TT 求出打开大门最少需要花费多少单位的钱。
输入格式
第一行包含两个正整数 n,m,意义见题目描述。
第二行包含 n 个正整数,第 i 个数表示 Fi。
接下来有 m 行,每行有三个正整数 u,v,w,表示从 u 到 v 有一个可以使用 w 次的传送门。
输出格式
输出仅一行包含一个整数表示答案。
样例数据 1
输入
4 3
5 5 5 5
1 2 1
3 2 1
3 4 1
输出
17
备注
【数据范围】
有 20% 的数据满足 n≤10,m≤50;
有 50% 的数据满足 n≤1000,m≤10000;
100% 的数据满足 1≤n≤10000,1≤m≤100000;
对于所有的 u,v,满足 1≤u,v≤n;u≠v;对于所有的 w,Fi,满足 1≤w,Fi≤50000。
以上的每类数据中都存在 50% 的数据满足对于所有的 w,Fi,有 w=Fi=1。
分析:
我们先来预热一下,讲讲最小路径覆盖
最小路径覆盖:用最少的路径覆盖完所有点
条件:必须是有向无环图
分类:
- 最小不相交路径覆盖:每条路径所经过的端点不相同,如下图,其最小路径覆盖数为3。即1->3>4,2,5。(单独一个点也算一条路径)
- 最小可相交路径覆盖:每条路径所经过的端点可以相同,如下图其最小路径覆盖数为2。即1->3->4,2->3>5。
然后由于蒟蒻没学第2种,我们就只讲一下第1个怎么求。
公式:ans = n - 最大匹配数
我们将每一个点都拆成两个点去看,一个入口一个出口,然后两个点若有路径相连,比如 u - > v ,则把 u 的出口和 v 的入口连起来,下面画一个图(上一个图拆分点后的样子)
大概就是这样子,然后我们虚拟一个源点 s 和汇点 t ,连边,变成这个样子
然后跑一个最大流,得到出口和入口的最大匹配数
这有什么用呢?先别急
我们现在假设每个点都有一条路径只经过他本身,那么总共就有 n 条路径,而我们每次在二分图里找一条匹配边就相当于把两条路径合成了一条路径,也就相当于路径数减少了1。所以找到了几条匹配边,路径数就减少了多少。所以有最小路径覆盖=原图的结点数-新图的最大匹配数。然后就over啦,是不是超级棒
现在回到这道题,我们发现,这就是一个加权最小路径覆盖嘛。
从源点到每个出口连一条值为 f [ i ] 的边,相当于 i 这个点要被经过 f [ i ] 次
对于传送门,相当于每个出口到其对应的入口之间连一条值为 w [ i ] 的边,意思是只能用这么多次
然后每个入口到汇点连一条值为 f [ i ] 的边,也相当于 i 这个点要被经过 f [ i ] 次
最后跑一个最大流,就好啦,我用的是dinic
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#define N 20009
#define M 300009
#define inf 1e9
using namespace std;
int n,m;
int nxt[M],head[N],to[M],cap[M],cnt=1,end;
int cur[N],lev[N];
int que[4*N],qn;
void add(int x,int y,int z){
nxt[++cnt]=head[x];head[x]=cnt;to[cnt]=y;cap[cnt]=z;
nxt[++cnt]=head[y];head[y]=cnt;to[cnt]=x;cap[cnt]=0;
}
bool bfs(){
int i,j,k;
for(i=0;i<=end;++i) lev[i]=-1,cur[i]=head[i];
que[qn=1]=0;lev[0]=0;
for(i=1;i<=qn;++i){
int u=que[i];
for(int e=head[u];e;e=nxt[e]){
int v=to[e];
if(cap[e]>0&&lev[v]==-1){
lev[v]=lev[u]+1;
que[++qn]=v;
if(v==end) return true;
}
}
}
return false;
}
int dinic(int u,int flow){
if(u==end) return flow;
int delta,res=0;
for(int &e=cur[u];e;e=nxt[e]){
int v=to[e];
if(cap[e]>0&&lev[v]>lev[u]){
delta=dinic(v,min(flow-res,cap[e]));
if(delta){
res+=delta;
cap[e]-=delta;cap[e^1]+=delta;
if(res>=flow) break;
}
}
}
if(res<=flow) lev[u]=-1;
return res;
}
int main(){
scanf("%d%d",&n,&m);
int i,j,k,ans=0;
end=2*n+1;
for(i=1;i<=n;++i){
scanf("%d",&k);
ans+=k;
add(0,i,k);add(i+n,end,k);
}
for(i=1;i<=m;++i){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v+n,w);//一定要注意编号的问题
}
int maxflow=0;
while(bfs())
maxflow+=dinic(0,inf);
ans-=maxflow;
printf("%d",ans);
return 0;
}