[BZOJ1927]SDOI2010星际竞速|费用流

还是比较显然的费用流,不过一开始建图想错了。。把每个点i拆成i1,i2,若ij有权值为q的边那就i1j2连费用q流量1的边,源向每个i1连流量1费用0的边,向每个i2连流量1费用为瞬时移动到这个点的能量的费用的边,每个i2向汇连流量1,费用0的边,最小费用流就是答案。。可以用类似最小路径覆盖的思路去理解,连向汇的边是保证每个点都经过一次,源连向i2的边表示直接瞬移过去,i1连向j2的边表示从图上走过去,这样每个点有且只有一个到达的费用。。

#include<iostream>
#include<cstdio>
#define N 1610
#define M 15005
#define inf 99999999
using namespace std;
struct edge{
	int e,q,f,next;
} ed[M*4];
int n,m,s,e,q,i,ne=1,nd,t,j,a[N],d[N],que[N*10],inq[N],last[N],road[N],minf[N];
void add(int s,int e,int f,int q)
{
	ed[++ne].e=e;ed[ne].f=f;
	ed[ne].q=q;
	ed[ne].next=a[s];a[s]=ne;
}
bool spfa(int s,int t)
{
	int head=1,tail=1,hh=1,tt=1,get,i,j,to;
	for (i=0;i<=nd;i++) inq[i]=0,last[i]=road[i]=-1,d[i]=inf;
	que[1]=s;inq[s]=1;d[s]=0;minf[s]=inf;
	while (hh<=tt)
	{
		get=que[head++];hh++;
		if (head>16000) head=1;
		for (j=a[get];j;j=ed[j].next)
			if (ed[j].f&&ed[j].q+d[get]<d[to=ed[j].e])
			{
				d[to]=d[get]+ed[j].q;
				minf[to]=min(minf[get],ed[j].f);
				last[to]=get;road[to]=j;
				if (!inq[to])
				{
					tail++;tt++;
					if (tail>16000) tail=1;
					que[tail]=to;inq[to]=1;
				}
			}
		inq[get]=0;
	}
	return last[t]!=-1;
}
int fyl(int s,int t)
{
	int i,ans=0,j;
	while (spfa(s,t))
	{
		i=t;ans+=minf[t]*d[t];
		while (last[i]!=-1)
		{
			j=road[i];
			ed[j].f-=minf[t];ed[j^1].f+=minf[t];
			i=last[i];
		}
	}
	return ans;
}
int main()
{
	freopen("1927.in","r",stdin);
	scanf("%d%d",&n,&m);
	nd=2*n+1;
	for (i=0;i<=nd;i++) a[i]=0;
	for (i=1;i<=n;i++)
	{
		scanf("%d",&t);
		add(0,i,1,0);add(i,0,0,0);
		add(0,i+n,inf,t);add(i+n,0,0,-t);
	}
	for (i=1;i<=m;i++)
	{
		scanf("%d%d%d",&s,&e,&q);
		if (s>e) swap(s,e);
		add(s,e+n,1,q);add(e+n,s,0,-q);
	}
	for (i=1;i<=n;i++) 
		add(i+n,nd,1,0),add(nd,i+n,0,0);
	printf("%d\n",fyl(0,nd));
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值