POJ-2175-Evacuation Plan

378 篇文章 0 订阅

这个题出的太好了~ 最开始以为是书上说的最小费用流做,但却果断的TLE了。后来网上参考别人的做法,才知道是消圈做,也就是说最后输出的答案不一定是最小费用的,只要比当前费用低就行了,所以根据最小费用流的证明,若当前的剩余图存在负圈,那么当前的费用一定不是最小的。则我们可以找到这个圈,然后对该圈的每个值进行流量为1的更新操作即可。需要注意的是spfa找圈的时候返回的不一定是圈内的点,所以先要找到圈内的点,然后在循环枚举所有圈内点的过程中也要注意不能有圈外的点出现(在这里WA了很久)

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<cmath>
using namespace std;
const int inf=1<<28;
const int maxn=100000;
int n,m,x[maxn],y[maxn],b[maxn],p[maxn],q[maxn],c[maxn],s[110][110];
int e,st,des,head[maxn],pnt[maxn],cost[maxn],flow[maxn],nxt[maxn];
int dist[maxn],pre[maxn],ffto[maxn],cnt[maxn];
bool vis[maxn];
queue<int> pq;
void AddEdge(int u,int v,int c,int f1,int f2)
{
    pnt[e]=v;nxt[e]=head[u];cost[e]=c;flow[e]=f1;head[u]=e++;
    pnt[e]=u;nxt[e]=head[v];cost[e]=-c;flow[e]=f2;head[v]=e++;
}
int Dis(int i,int j)
{
    return abs(x[i]-p[j])+abs(y[i]-q[j])+1;
}
int Spfa()
{
    while(!pq.empty())
	pq.pop();
    for(int i=0;i<=des;i++)
	dist[i]=inf;
    memset(vis,0,sizeof(vis));
    memset(cnt,0,sizeof(cnt));
    memset(pre,-1,sizeof(pre));
    dist[st]=0;
    vis[st]=1;
    cnt[st]++;
    pq.push(st);
    while(!pq.empty())
    {
	int u=pq.front();
	vis[u]=0;
	pq.pop();
	for(int i=head[u];i!=-1;i=nxt[i])
	    if(flow[i]&&dist[pnt[i]]>dist[u]+cost[i])
	    {
		dist[pnt[i]]=dist[u]+cost[i];
		pre[pnt[i]]=u;
		if(!vis[pnt[i]])
		{
		    pq.push(pnt[i]);
		    vis[pnt[i]]=1;
		    if(++cnt[pnt[i]]>n+m+2)
			return pnt[i];
		}

	    }
    }
    return -1;
}
void solve()
{
    int ss=Spfa();
    int sta=ss;
    if(ss==-1)
    {
	printf("OPTIMAL\n");
	return;
    }
    printf("SUBOPTIMAL\n");
    memset(vis,0,sizeof(vis));
    for(;!vis[ss];vis[ss]=1,ss=pre[ss]);
    sta=ss;
    do
    {
	int from=pre[sta],to=sta;
	if(from<=n&&to>n)
	    s[from][to-n]++;
	if(to<=n&&from>n)
	    s[to][from-n]--;
	sta=pre[sta];	
    }while(sta!=ss);
    for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	    printf("%d%c",s[i][j],j==m?'\n':' ');

}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
	memset(head,-1,sizeof(head));
	memset(ffto,0,sizeof(ffto));
	e=st=0,des=n+m+1;
	for(int i=1;i<=n;i++)
	    scanf("%d%d%d",&x[i],&y[i],&b[i]);
	for(int i=1;i<=m;i++)
	    scanf("%d%d%d",&p[i],&q[i],&c[i]);
	for(int i=1;i<=n;i++)
	    for(int j=1;j<=m;j++)
	    {
		scanf("%d",&s[i][j]);
		ffto[j]+=s[i][j];
		AddEdge(i,n+j,Dis(i,j),inf-s[i][j],s[i][j]);
	    }
	for(int i=1;i<=n;i++)
	    AddEdge(st,i,0,b[i],0);
	for(int i=1;i<=m;i++)
	    AddEdge(n+i,des,0,c[i]-ffto[i],ffto[i]);
	solve();
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值