用这道题学了最小费用最大流,明白了一些建模的思路。
我们必须要让一个合法解与一个割集的对应。拿此题来说,原命题可以转换为要让所有点进且仅进一次,至多出一次;分别限制即可。
进且仅进一次意味着开一条边容量为1而存在一条路径从源到汇使得有且仅有这条边容量为正无穷;至多出一次意味着开一条边,这条边到源点的流至多为1.
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#include<cmath>
#include<iostream>
inline int in(){
char c=getchar();
int x=0;
while(c<'0'||c>'9')c=getchar();
for(;c>='0'&&c<='9';c=getchar())x=x*10+c-'0';
return x;
}
struct ES{
int cost,c,from,to;
}e[40000];
#include<vector>
int p[1602],next[40000],tot=2;
//0是源点,1601是汇点
inline void addedge(int cost,int c,int from,int to){
e[tot]=(ES){cost,c,from,to};
next[tot]=p[from],p[from]=tot++;
}
int main(){
freopen("starrace.in","r",stdin);
freopen("starrace.out","w",stdout);
int n=in(),m=in(),i,u,v;
for(i=1;i<=n;++i){
addedge(in(),1,0,i<<1),addedge(-e[tot-1].cost,0,i<<1,0);
addedge(0,1,i<<1,1601),addedge(0,0,1601,i<<1);
addedge(0,1,0,(i<<1)-1),addedge(0,0,(i<<1)-1,0);
}
for(i=0;i<m;++i){
u=in(),v=in();
if(u==v)continue;
if(u>v)swap(u,v);
u=(u<<1)-1,v=v<<1;
addedge(in(),0x7fffffff,u,v);
addedge(-e[tot-1].cost,0,v,u);
}
int dis[1602],q[1602],h,t,d;
int path[1602];//path(i)表示更新到i的最短路的边。
int ans=0;
bool flag[1602]={0};
dis[0]=0;
for(;;){
//splay
memset(dis+1,0x7f,1601<<2);
q[0]=0,flag[0]=1;;
for(h=0,t=1;h!=t;){
flag[q[h]]=0,d=dis[q[h]],i=p[q[h]];
h=h==1601?0:h+1;
for(;i;i=next[i]){
if(e[i].c&&d+e[i].cost<dis[e[i].to]){
dis[e[i].to]=d+e[i].cost;
path[e[i].to]=i;
//cout<<i<<":"<<e[i].from<<"-("<<e[i].cost<<")>"<<e[i].to<<":"<<dis[e[i].to]<<endl;
if(!flag[e[i].to]){
if(h==t||dis[e[i].to]<dis[q[h]]){
h=h?h-1:1601;
q[h]=e[i].to;
}
else{
q[t]=e[i].to;
t=t==1601?0:t+1;
}
flag[e[i].to]=1;
}
}
//cout<<i<<" ";
}
//cout<<":("<<h<<","<<t<<"):";
//for(int i=h;i!=t;i=i==1601?0:i+1)cout<<q[i]<<" ";
//cout<<endl;
}
//cout<<"----------\n";
if(dis[1601]>1E9)break;
//增广
for(i=1601;i;i=e[path[i]].from){
//cout<<i<<"<-";
--e[path[i]].c,++e[path[i]^1].c;
ans+=e[path[i]].cost;
}
//cout<<i<<endl;
}
printf("%d\n",ans);
}