[SDOI2010星际竞速]解题报告

用这道题学了最小费用最大流,明白了一些建模的思路。

我们必须要让一个合法解与一个割集的对应。拿此题来说,原命题可以转换为要让所有点进且仅进一次,至多出一次;分别限制即可。

进且仅进一次意味着开一条边容量为1而存在一条路径从源到汇使得有且仅有这条边容量为正无穷;至多出一次意味着开一条边,这条边到源点的流至多为1.

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#include<cmath>
#include<iostream>
inline int in(){
	char c=getchar();
	int x=0;
	while(c<'0'||c>'9')c=getchar();
	for(;c>='0'&&c<='9';c=getchar())x=x*10+c-'0';
	return x;
}

struct ES{
	int cost,c,from,to;
}e[40000];
#include<vector>
int p[1602],next[40000],tot=2;
//0是源点,1601是汇点 
inline void addedge(int cost,int c,int from,int to){
	e[tot]=(ES){cost,c,from,to};
	next[tot]=p[from],p[from]=tot++;
}
int main(){
	freopen("starrace.in","r",stdin);
	freopen("starrace.out","w",stdout);
	int n=in(),m=in(),i,u,v;
	for(i=1;i<=n;++i){
		addedge(in(),1,0,i<<1),addedge(-e[tot-1].cost,0,i<<1,0);
		addedge(0,1,i<<1,1601),addedge(0,0,1601,i<<1);
		addedge(0,1,0,(i<<1)-1),addedge(0,0,(i<<1)-1,0);
	}
	for(i=0;i<m;++i){
		u=in(),v=in();
		if(u==v)continue;
		if(u>v)swap(u,v);
		u=(u<<1)-1,v=v<<1;
		addedge(in(),0x7fffffff,u,v);
		addedge(-e[tot-1].cost,0,v,u);
	}
	
	int dis[1602],q[1602],h,t,d;
	int path[1602];//path(i)表示更新到i的最短路的边。 
	int ans=0;
	bool flag[1602]={0};
	dis[0]=0;
	for(;;){
		//splay
		memset(dis+1,0x7f,1601<<2);
		q[0]=0,flag[0]=1;;
		for(h=0,t=1;h!=t;){
			flag[q[h]]=0,d=dis[q[h]],i=p[q[h]];
			h=h==1601?0:h+1;
			for(;i;i=next[i]){
				if(e[i].c&&d+e[i].cost<dis[e[i].to]){
					dis[e[i].to]=d+e[i].cost;
					path[e[i].to]=i;
					//cout<<i<<":"<<e[i].from<<"-("<<e[i].cost<<")>"<<e[i].to<<":"<<dis[e[i].to]<<endl;
					if(!flag[e[i].to]){
						if(h==t||dis[e[i].to]<dis[q[h]]){
							h=h?h-1:1601;
							q[h]=e[i].to;
						}
						else{
							q[t]=e[i].to;
							t=t==1601?0:t+1;
						}
						flag[e[i].to]=1;
					}
				}
				//cout<<i<<" ";
			}
			//cout<<":("<<h<<","<<t<<"):";
			//for(int i=h;i!=t;i=i==1601?0:i+1)cout<<q[i]<<" ";
			//cout<<endl;
		}
		//cout<<"----------\n";
		if(dis[1601]>1E9)break;
		//增广
		for(i=1601;i;i=e[path[i]].from){
			//cout<<i<<"<-";
			--e[path[i]].c,++e[path[i]^1].c;
			ans+=e[path[i]].cost;
		}
		//cout<<i<<endl;
	}
	printf("%d\n",ans);
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值