[SDOI2010]星际竞速

本文详细解析了一种基于最小费用流算法的建模方法,通过拆点的方式将问题转化为寻找最优路径的问题,并给出了具体的实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这道题和最小路径覆盖比较像,都是把点拆成x部和y部,最小路径覆盖直观地看可以说是在y部中给x部的点找后继,这个题直观地看可以说是在x部中给y部的点找前驱。以下建模方法摘自学长题解


思路和最小路径覆盖类似,先进行拆点,把每个点u拆成u和u‘。

对于跳跃模式【忘了叫什么模式了】就从源点往u'连一条流量为1,费用为边权的边。

对于星球间的航道(u,v)【假设u<v】就从u往v'连一条流量为1,费用为边权的边。

从源点往每一个点u连一条流量为1,费用为0的边,从每个点u’往汇点连一条流量为1,费用为0的边。

这样保证每个点都能被经过。流量为1保证了每个点最多被经过一次。

然后跑一遍最小费用最大流,输出费用即可。


这样的话对于每个y部的点,流入它的满流的边对应的起点就是它的前驱。这个建模方法由于y部向T边流量为1,S向x部流量也为1,保证了每个点最多只有一个前驱,最多只有一个后继。至于跳跃,可以把它看成从一个点免费跳到源点再有代价跳到目标点。

还有就是因为vector实在是太慢最近正在习惯用邻接表写网络流,因为反边的性质边要从偶数开始存,然而我还习惯h[]初始值全部=0,这个错误会忽略一条边,在弱的样例中不容易发现,以后还是从2开始存吧

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define INF (2100000000)
#define fr(i,s,t) for (i=s;i<=t;i++)
using namespace std;
struct edge{
	int next,from,to,cap,flow,cost;
	edge(){}
	edge (int x,int x1,int x2,int x3,int x4,int x5): 
	next(x),from(x1),to(x2),cap(x3),flow(x4),cost(x5){}
}q[60010];
int n,m,h[4010],T,m1=1,p[4010],a[4010],d[4010];
bool inq[4010];
void addedge(int x,int y,int cost){
	q[++m1]=edge(h[x],x,y,1,0,cost);
	h[x]=m1;
	q[++m1]=edge(h[y],y,x,0,0,-cost);
	h[y]=m1;
}
bool SPFA(int &cost){
	memset(p,0,sizeof(p));
	memset(a,0,sizeof(a));
	memset(inq,0,sizeof(inq));
	int x,y,i;
	
	a[0]=INF; inq[0]=1;
	fr(i,1,T) d[i]=INF; 
	queue <int> Q;
	Q.push(0);
	
	while (!Q.empty()){
		x=Q.front(); Q.pop();
		inq[x]=0;
		for (i=h[x];i;i=q[i].next){
			if (q[i].cap==q[i].flow) continue;
			y=q[i].to;
			if (d[x]+q[i].cost>=d[y]) continue;
			d[y]=d[x]+q[i].cost;
			p[y]=i;
			a[y]=min(a[x],q[i].cap-q[i].flow);
			if (!inq[y]){
				inq[y]=1;
				Q.push(y);
			}
		}
	}
	if (d[T]==INF) return 0;
	cost+=d[T]*a[T];
	for (i=T;i;i=q[p[i]].from){
		q[p[i]].flow+=a[T];
		q[p[i]^1].flow-=a[T];
	}
	return 1;
}
int main(){
	int i,x,y,z,cost=0;
	scanf("%d %d",&n,&m);
	fr(i,1,n) scanf("%d",&x),addedge(0,n+i,x);
	fr(i,1,m){
		scanf("%d %d %d",&x,&y,&z);
		if (x>y) swap(x,y);
		addedge(x,n+y,z);
	}
	T=2*n+1;
	fr(i,1,n){
		addedge(0,i,0);
		addedge(n+i,T,0);
	}
	while (SPFA(cost));
	cout<<cost;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值