HDU5960 可持久化左偏树 k短路问题

题目大意

有一堆项目,每个项目有三个权值 ( c o s t 0 , c o s t 1 , c o l o r ) (cost_0,cost_1,color) (cost0,cost1,color),你要选择一些项目顺序执行,并给出你总花费的计算方式,求花费第k 的工作方式。

题目分析

建图

转化为求k短路的话,必须要把权值取相反数,然后答案取相反数。

把每个项目 i i i拆成三个点 i 1 i_1 i1, i 2 i_2 i2, i 3 i_3 i3,连边 ( i 1 , i 3 , − c o s t 0 ) ( i 2 , i 3 , − c o s t 1 ) (i_1,i_3,-cost_0)(i_2,i_3,-cost_1) (i1,i3,cost0)(i2,i3,cost1)表示两种花钱方式,根据i的color连边 ( i 3 , ( i + 1 ) 1 , 0 ) (i_3,(i+1)_1,0) (i3,(i+1)1,0) ( i 3 , ( i + 1 ) 1 , 0 ) (i_3,(i+1)_1,0) (i3,(i+1)1,0),连边 ( i 1 , ( i + 1 ) 1 , 0 ) ( i 2 , ( i + 1 ) 2 , 0 ) (i_1,(i+1)_1,0)(i_2,(i+1)_2,0) (i1,(i+1)1,0)(i2,(i+1)2,0)表示不做这个项目.
起点为 1 1 1_1 11,新建终点, n 1 n_1 n1, n 2 n_2 n2, n 3 n_3 n3连到终点上。

可持久化左偏树

首先由于图的性质,可以通过拓扑计算从终点到其他点在反向图上的最短路,即为该点在正向图上走最短路到终点的距离 d i s dis dis.这一步上,我们也建立出了一棵终点的最短路树,分出了非树边与树边。

然后我们希冀每走一步就能获得一条新的当前最短路径的算法。

我们知道,走一条路,那便是走若干树边和若干非树边.所以我们的更新可以是每次在当前路径的基础上多走一条非树边。

设非树边 ( u , v ) (u,v) (u,v)的权值为 d i s v + w ( u , v ) − d i s u dis_v+w(u,v)-dis_u disv+w(u,v)disu,即减去原来这一段都走树边的贡献,加上走一条非树边的贡献。

利用可持久化左偏树(每次合并的时候新建节点即可),对于每一个点维护一棵小根堆左偏树,里面储存从该点走树边走到终点的路径上,与这条路径相连的非树边有哪些。

首先,第1短路是从S开始不走非树边的路。开一个优先队列,来维护现在的“第 t t t短路候选路径”,求第 t t t短路时,就从这个候选队列里取出最短的一条路径作为第 t t t短路径。在确定了第 t t t短路径后,有几条比该路径稍长的路径也该被加入候选队列中,它们是:

  1. 在走了第 t t t短路径上的最后一条非树边后,假设我站在 v v v这个点上,那么我在我剩下的前进道路中,再选一条非树边走一下。
  2. 不走第 t t t短路径的最后一条非树边,而是走它在堆中的左儿子/右儿子。

于是这个候选队列中存放的候选路径的信息就只有,路径长度和走的最后一条非树边了。

可能有点没讲清楚,具体可以看一下work函数。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
#define LL long long
#define pr pair<LL,int>
const int N=150005;
const LL inf=1e15;
int T,n,K,s,t,t0,t1,tot,top;
int h0[N],h1[N],pre[N],rt[N],st[N],du[N];LL dis[N];
struct edge{int w,to,ne;}e0[N<<1],e1[N<<1];
void add(int x,int y,int z) {
	e0[++t0].to=y,e0[t0].ne=h0[x],h0[x]=t0,e0[t0].w=z;
	e1[++t1].to=x,e1[t1].ne=h1[y],h1[y]=t1,e1[t1].w=z;
	++du[x];
}
void bg() {//build_graph
	s=n+1,t=n*3+1;int c0,c1,co;
	t0=t1=0;for(int i=1;i<=t;++i) h0[i]=h1[i]=du[i]=0,dis[i]=inf;
	for(int i=1;i<=n;++i) {
		c0=read(),c1=read(),co=read();
		add(n+i,i,-c0),add(n+n+i,i,-c1);
		if(i!=n) {
			add(n+i,n+i+1,0),add(n+n+i,n+n+i+1,0);
			if(co) add(i,n+n+i+1,0);
			else add(i,n+i+1,0);
		}
		else add(n,t,0),add(n+n,t,0),add(n+n+n,t,0);
	}
}
void topo() {
	top=1,st[top]=t,dis[t]=pre[t]=0;
	while(top) {
		int x=st[top--];
		for(int i=h1[x];i;i=e1[i].ne) {
			if(dis[e1[i].to]>dis[x]+e1[i].w)
				dis[e1[i].to]=dis[x]+e1[i].w,pre[e1[i].to]=x;
			--du[e1[i].to];if(!du[e1[i].to]) st[++top]=e1[i].to;
		}
	}
}
struct node{int ls,rs,d,id;LL v;}a[N*40];
int merge(int x,int y) {
	if(x*y==0) return x+y;
	if(a[x].v>a[y].v) swap(x,y);
	int o=++tot;a[tot]=a[x];
	a[o].rs=merge(a[o].rs,y);
	if(a[a[o].ls].d<a[a[o].rs].d) swap(a[o].ls,a[o].rs);
	a[o].d=a[a[o].rs].d+1;
	return o;
}
void dfs(int x) {//每个左偏树代表从i到t的路上的所有非树边
	if(pre[x]) rt[x]=rt[pre[x]];
	for(int i=h0[x];i;i=e0[i].ne) {
		int v=e0[i].to;
		if(dis[v]==inf||pre[x]==v) continue;
		a[++tot]=(node){0,0,1,v,dis[v]+e0[i].w-dis[x]},rt[x]=merge(rt[x],tot);
	}
	for(int i=h1[x];i;i=e1[i].ne)
		if(pre[e1[i].to]==x) dfs(e1[i].to);
}
void work() {
	tot=0,rt[t]=0,dfs(t);
	if(K==1) {printf("%lld\n",-dis[s]);return;}
	priority_queue<pr,vector<pr >,greater<pr > > q;
	q.push(pr(dis[s]+a[rt[s]].v,rt[s]));
	while(K--) {//利用优先队列获得答案
		pr kl=q.top(); q.pop();
		if(K==1) {printf("%lld\n",-kl.first);return;}
		int u=kl.second,v=a[u].id;
		if(rt[v]) q.push(pr(kl.first+a[rt[v]].v,rt[v]));
		if(a[u].ls) q.push(pr(kl.first-a[u].v+a[a[u].ls].v,a[u].ls));
		if(a[u].rs) q.push(pr(kl.first-a[u].v+a[a[u].rs].v,a[u].rs));
	}
}
int main() {
	T=read();
	while(T--) {
		n=read(),K=read();
		bg(),topo(),work();
	}
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值