bzoj1927: [Sdoi2010]星际竞速
最小费用最大流。
拆点法建模,一个点分为u0,u1。
虚拟源点S与每个u0连一条容量为1,费用为a[u]的边,与每个u1连一条容量为1,费用为0的边。
每个u0与虚拟汇点T连一条容量为1,费用为0的边。
每个u能到达v的点u1和v0连一条容量为1,费用为w的边。
为什么?
1.如果一个点是用瞬间移动到达的,就相当与从S点瞬间移动。
2.如果一个点是航路连去的,相当于从另一个点的u1连过去,因为s流到u1没有费用,所以费用就是w。
建模太巧妙了。
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int maxn = 1600 + 10; const int maxm = 300000 + 10; const int INF = 0x3f3f3f3f; int n,m; int g[maxn],v[maxm],next[maxm],f[maxm],d[maxm],eid=0,vid=0; int id[maxn][2],q[maxm],pre[maxn],l,r,u; int dist[maxn]; bool inque[maxn]; int S,T; void addedge(int a,int b,int c,int D) { v[eid]=b; next[eid] = g[a]; f[eid]=c; d[eid]=D; g[a]=eid++; v[eid]=a; next[eid] = g[b]; f[eid]=0; d[eid]=-D; g[b]=eid++; } void build() { memset(g,-1,sizeof(g)); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) id[i][0] = ++vid,id[i][1]=++vid; S=++vid; T=++vid; for(int i=1,a;i<=n;i++) { scanf("%d",&a); addedge(S,id[i][1],1,0); addedge(S,id[i][0],1,a); addedge(id[i][0],T,1,0); } for(int i=1,u,v,w;i<=m;i++) { scanf("%d%d%d",&u,&v,&w); if(u>v) swap(u,v); addedge(id[u][1],id[v][0],1,w); } } bool spfa() { memset(dist,0x3f,sizeof(dist)); l=r=0; dist[q[r++]=S]=0; while(l<r) { inque[u=q[l++]]=0; for(int i=g[u];~i;i=next[i]) if(f[i] && dist[v[i]] > dist[u]+d[i]) { dist[v[i]] = dist[u]+d[i]; pre[v[i]]=i; if(!inque[v[i]]) inque[q[r++]=v[i]]=1; } } return dist[T]<INF; } int augment() { int aug=INF,res=0; for(int i=T;i!=S;i=v[pre[i]^1]) { aug=min(aug,f[pre[i]]); } for(int i=T;i!=S;i=v[pre[i]^1]) f[pre[i]]-=aug,f[pre[i]^1]+=aug,res+=aug*d[pre[i]]; return res; } void solve() { int res=0; while(spfa()) res+=augment(); printf("%d\n",res); } int main() { build(); solve(); return 0; }