用这道题学了最小费用最大流,明白了一些建模的思路。
我们必须要让一个合法解与一个割集的对应。拿此题来说,原命题可以转换为要让所有点进且仅进一次,至多出一次;分别限制即可。
进且仅进一次意味着开一条边容量为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); }