【bzoj4289: PA2012 Tax】图论--建图

4289: PA2012 Tax

Time Limit: 10 Sec   Memory Limit: 128 MB
Submit: 701   Solved: 222
[ Submit][ Status][ Discuss]

Description

给出一个N个点M条边的无向图,经过一个点的代价是进入和离开这个点的两条边的边权的较大值,求从起点1到点N的最小代价。起点的代价是离开起点的边的边权,终点的代价是进入终点的边的边权
N<=100000
M<=200000

Input

Output

Sample Input

4 5
1 2 5
1 3 2
2 3 1
2 4 4
3 4 8

Sample Output

12




这题有个显然的暴力做法,就是把每条无向边拆成两条有向边,然后把这些有向边当做点,重新建图,对于原图中的点x,每一条入边p,要向每一条出边q连边,边的权值为p和q这两条边权值的较大值,补上源点和汇点,跑最短路就可以了。暴力的效率是O(m*m)。

我们考虑进行优化。对于暴力的做法,我们发现其中每条入边要向每条出边(这里指的入边和出边都转化为后来的点)连边,这一步直接制约了我们的效率。我们考虑如何优化这一步:

对于一个点x,我们把所有的出边按权值排序,容易知道,我们可以从权值小的边向权值大的边(这里指的连边都是把原图里的边转化为点后进行的)连边,边的权值是大权值-小权值,而对于权值大的边到权值小的边我们可以连一条权值为0的边(因为如果经过了权值较大的边,那么再经过权值较小的边时对答案的贡献仍然是那个较大的权值)。之后我们把入边加入,每条入边向对应的出边(y->x向x->y)连一条权值为原图中改边权值的边。

这样我们就把建图简化了,再加上源点和汇点就可以跑最短路了。

#include<cstdio>
#include<queue>
#include<vector>
#include<algorithm>
#include<cstring>
#define Pa pair<long long,int>
#define ll long long
#define N 2000005
#define INF 1LL<<60
using namespace std;
int k=1,K=1,fir[N],Fir[N],st,ed,l,r,c,n,m,b[N];
ll dis[N]; 
struct he{
	int r,c,nx;
}a[N],A[N];
bool cmp(int u,int v){
	return a[u].c<a[v].c;
}
void add(int l,int r,int c){
	k++;a[k].r=r;a[k].c=c;a[k].nx=fir[l];fir[l]=k;
}
void Add(int l,int r,int c){
	K++;A[K].r=r;A[K].c=c;A[K].nx=Fir[l];Fir[l]=K;
}
ll dij(int st,int ed){
	priority_queue<Pa,vector<Pa>,greater<Pa> >Q;
	for(int i=st;i<=ed;i++) dis[i]=INF; 
	Q.push(make_pair(0,st));dis[st]=0;
	while(!Q.empty()){
		int u=Q.top().second;
		ll Dis=Q.top().first;
		Q.pop();
		if(Dis>dis[u]) continue;
		for(int i=Fir[u];i!=-1;i=A[i].nx)
		if(dis[u]+A[i].c<dis[A[i].r])
		dis[A[i].r]=dis[u]+A[i].c,Q.push(make_pair(dis[A[i].r],A[i].r));
	}
	return dis[ed];
}
void rebuild(){
	st=1;ed=2*(m+1);
	for(int i=1;i<=n;i++){
		int cnt=0;
		for(int j=fir[i];j!=-1;j=a[j].nx)
			b[++cnt]=j;
		sort(b+1,b+1+cnt,cmp);
		for(int j=1;j<=cnt;j++){
			int x=b[j],y=b[j+1];
			if(a[x].r==n) Add(x,ed,a[x].c);
			if(i==1) Add(st,x,a[x].c);
			Add(x^1,x,a[x].c);
			if(j<cnt) Add(x,y,a[y].c-a[x].c),Add(y,x,0);
		}
	}
}
int main(){
	freopen("1.in","r",stdin);
	freopen("1.out","w",stdout);
	memset(fir,-1,sizeof(fir));
	memset(Fir,-1,sizeof(Fir));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&l,&r,&c);
		add(l,r,c);add(r,l,c);
	}
	rebuild();
	printf("%lld",dij(st,ed));
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值