这题想了很久,今天才想明白。。
写下我的心路历程。供大家参考吧,希望能别像我这样这么久才明白。
题意:
n栋大楼,每个大楼里ni个人,m个避难所,每个避难所能容纳mi个人。每个建筑物都有坐标,每个人到达避难所的费用是距离+1。给了你一种安排,问这种安排是不是最优的。
题解:
消圈定理:残留网络里如果存在负费用圈,那么当前流不是最小费用流。因为你绕着这个圈走一遍,费用更小了。
所以题中给的到底是不是最优的,那么我们就在他建的图的残留网络里找有没有负费用圈就好了。
因为大楼i到避难所j的边容量肯定是MX,所以残留网络一定有i到j的边,费用为距离。所以建边 add(i,j+n,p)。
如果大楼i到避难所j有人去避难,那么残路网络里一定有反边。所以建边add(j+n,i,-p)。
如果避难所j有人来避难,那么残留网络里一定有j到汇点的反边。所以建边add(0,j+n,0)。我把汇点设为0了。
如果避难所j来避难的人数少于他所能承受的人数,那残留网络里一定有残留的正边。所以建边add(j+n,0,0)。
至此,建边结束。 你首先要明白这题要是他不给你他的方案,你要怎么建边,然后你才能知道残留网络是什么。
然后根据图用spfa找负圈。有负圈的话从在spfa跳出的那点开始,找一个圈,因为我们假设从v跳出的spfa,但v不一定在这个圈里。所以我们要自己找圈。
找到圈后,开始更改流量,把这个圈里的所有边的流量+1。pre[now] 到 now这个边,如果 pre[now] >n,就说明pre[now]这点是避难所,now是大楼,这条边是反边,所以map里这个值-1,因为这里存的是正边的流量。同理 如果pre[now]<=n,就说明pre[now]这点时大楼,now是避难所,这条是正边,map的值+1。map里面存的都是正边的流量。
结束了。
#include<stdio.h> #include<string.h> #define MX 10000000 #define nMax 300 #define eMax 55555 struct Egde{ int u,v,w,next,pre; // u起点 v终点 c容量 f流量 w费用 next下一条边 pre反边 }eg[eMax]; struct building{ int x,y,numb; }build[nMax]; struct shelter{ int x,y,numb; }shel[nMax]; int n, m, ans,vs,vt,map[nMax][nMax]; int k, list[nMax]; int que[nMax*nMax], pre[nMax], dis[nMax]; int cnt[nMax]; bool vis[nMax]; void add(int u, int v, int w){//u起点,v终点,c容量,w费用 eg[k].u=u;eg[k].v=v;eg[k].next=list[u];eg[k].w=w;list[u]=k++; } int spfa(){ int i, head = 0, tail = 1; for(i = vs; i <= vt; i ++){//源点到汇点初始化 dis[i] = MX; vis[i] = false; cnt[i]=0; } dis[vs] = 0; que[0] = vs; vis[vs] = true; cnt[vs]++; while(tail > head){ int u = que[head ++]; for(i = list[u]; i != -1; i = eg[i].next){ int v = eg[i].v; if(dis[v] > dis[u] + eg[i].w){ dis[v] = dis[u] + eg[i].w; pre[v] = u; if(!vis[v]){ vis[v]=true; que[tail ++] = v; if(++cnt[v]>vt){ return v; } } } } vis[u] = false; } return -1; } int abs(int a){ return a>0?a:-a; } int distance(int u,int v){ return abs(build[u].x-shel[v].x)+abs(build[u].y-shel[v].y); } int main(){ while(scanf("%d%d",&n,&m)!=EOF){ int number[nMax]; memset(list,-1,sizeof(list)); ans = 0; k=0; for(int i=1;i<=n;i++){ scanf("%d%d%d",&build[i].x,&build[i].y,&build[i].numb); } for(int i=1;i<=m;i++){ scanf("%d%d%d",&shel[i].x,&shel[i].y,&shel[i].numb); } memset(number,0,sizeof(number)); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ scanf("%d",&map[i][j]); add(i,j+n,distance(i,j)); number[j]+=map[i][j]; if(map[i][j]){ add(j+n,i,-distance(i,j)); } } } for(int i=1;i<=m;i++){ if(number[i]){ add(0,i+n,0); if(number[i]<shel[i].numb){ add(i+n,0,0); } } } vs=0; vt=n+m; int u=spfa(); if(u==-1){ printf("OPTIMAL\n"); } else{ printf("SUBOPTIMAL\n"); memset(vis,0,sizeof(vis)); int v=u,now; while(vis[pre[v]]==0){ vis[v]=1; v=pre[v]; } now=v; do{ if(pre[now]<=n && now>n){ map[pre[now]][now-n]++; } if(pre[now]>n && now<=n){ map[now][pre[now]-n]--; } now=pre[now]; } while(now!=v); for(int i=1;i<=n;i++){ for(int j=1;j<m;j++){ printf("%d ",map[i][j]); } printf("%d\n",map[i][m]); } } } return 0; }
边开小了。。RE。
队列开小了。。TLE好久。。
一开始map中 pre[now]忘记-n了
distance 中 abs直接加在最外边了。。却忘了有可能一正一负。。。