P2483 【模板】k 短路 / [SDOI2010] 魔法猪学院AC题解

本人 6年级 耗时2个月luoguP2483 【模板】k 短路 / [SDOI2010] 魔法猪学院AC

接下来由我为大家讲解

对于原图以 𝑡t 为根建出任意一棵最短路径树 𝑇T,即反着从 𝑡t 跑出到所有点的最短路 𝑑𝑖𝑠dis

它有一些性质:

性质1:

对于一条 𝑠s 到 𝑡t 的路径的边集 𝑃P,去掉 𝑃P 中和 𝑇T 的交集,记为 𝑃′P′。

那么 𝑃′P′ 对于中任意相邻(从 𝑠s 到 𝑡t 的顺序)的两条边 𝑒,𝑓e,f,满足 𝑓f 的起点在 𝑇T 中为 𝑒e 的终点的祖先或者为相同点。

因为 𝑃P 中 𝑒,𝑓e,f 之间由树边相连或者直接相连。

性质2:

对于不在 𝑇T 中的边 𝑒e ,设 𝑢u 为起点,𝑣v 为终点,𝑤w为权值。

定义 Δ𝑒=𝑑𝑖𝑠𝑣+𝑤−𝑑𝑖𝑠𝑢Δe​=disv​+w−disu​,即选这条边的路径和最短路的长度的差

设 𝐿𝑃LP​ 表示路径长度,则有

𝐿𝑃=𝑑𝑖𝑠𝑠+∑𝑒∈𝑝′Δ𝑒LP​=diss​+e∈p′∑​Δe​

这很显然。

性质3:

对于满足性质 11 的 𝑃′P′的定义的边集 𝑆S,有且仅有一条 𝑠s 到 𝑡t 的路径的边集 𝑃P,使得 𝑃′=𝑆P′=S。

因为树 𝑇T 上的两个点之间有且仅有一条路径。

问题转化

求第 𝑘k 小的满足性质 11 的 𝑃′P′的定义的边集

算法

用小根堆维护边集 𝑃P

初始 𝑃P 为空集(实际上只要维护边集当前尾部的边的起点是哪一个就好了,空集即 𝑠s)

每次取出最小权值的边集 𝑃P,设当前尾部的边的起点为 𝑥x

有两种方法可以得到一个新的边集:

**1.**替换 𝑥x 为起点的这条边为一条刚好大于等于它的非树边。

**2.**尾部接上一条起点为以 𝑥x 为起点的这条边的终点在 𝑇T 中祖先(包括自己)连出去的所有非树边的最小边。

然后就是怎么维护祖先出去的所有非树边的最小边:

显然可以从祖先转移过来,直接可并堆即可。

又因为要保留每个点的信息,所以合并的时候可持久化即可

和线段树合并的可持久化一样,然后就可以过了。

接下来送出AC代码 代码为C++14 运行

感谢大家的收看

#include<bits/stdc++.h>
#define maxn 5005
#define maxm 200005
#define eps 1e-8
using namespace std;

int n,m;
double E;
int info[maxn],Prev[maxm],to[maxm],cnt_e=1;
double cst[maxm];
void Node(int u,int v,double c){ Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v,cst[cnt_e]=c; }
bool usd[maxn];
int fa[maxn],faed[maxn];
double dis[maxn];

	
struct node{
	int ch[2],to,dep;
	double cst;
	node(int to=0,double cst=0):to(to),cst(cst){dep=1,ch[0]=ch[1]=0;}
}t[maxm * 10];	
int cnt,rt[maxn];

int cpy(int now){
	t[++cnt] = t[now];
	return cnt;
}

void merge(int &now,int l,int r){
	if(!l || !r){ now = l+r; return; }
	if(t[l].cst < t[r].cst) now = cpy(l) , merge(t[now].ch[1],t[l].ch[1],r);
	else now = cpy(r) , merge(t[now].ch[1],l,t[r].ch[1]);
	if(t[t[now].ch[1]].dep > t[t[now].ch[0]].dep) swap(t[now].ch[0],t[now].ch[1]);
	t[now].dep = t[t[now].ch[1]].dep + 1;
}

void dfs(int now){
	for(int i=info[now];i;i=Prev[i])
		if(faed[to[i]] == i){
			//printf("%d %d\n",to[i],now);
			merge(rt[to[i]],rt[to[i]],rt[now]);
			dfs(to[i]);
		}
}


int main(){
	scanf("%d%d%lf",&n,&m,&E);
	for(int i=1,u,v;i<=m;i++){
		double c;
		scanf("%d%d%lf",&u,&v,&c);
		Node(v,u,c);
	}
	priority_queue<pair<double,int>,vector<pair<double,int> >,greater<pair<double,int> > >q;
	memset(dis,0x7f,sizeof dis);
	dis[n] = 0 , q.push(make_pair(dis[n],n));
	for(int now;!q.empty();){
		now = q.top().second , q.pop();
		if(usd[now]) continue;
		usd[now] = 1;
		for(int i=info[now];i;i=Prev[i])
			if(dis[to[i]] > dis[now] + cst[i] + eps){
				dis[to[i]] = dis[now] + cst[i] , fa[to[i]] = now;
				faed[to[i]] = i;
				q.push(make_pair(dis[to[i]],to[i]));
			}
	}
	//for(int i=1;i<=n;i++) printf("$%d %.3lf\n",fa[i],dis[i]);
	for(int i=1;i<=n;i++)
		for(int j=info[i];j;j=Prev[j])
			if(j != faed[to[j]] && to[j]!=n){
				t[++cnt] = node(i,dis[i] + cst[j] - dis[to[j]]);
				//printf("%d %d %.3lf\n",i,to[j],dis[i] + cst[j] - dis[to[j]]);
				merge(rt[to[j]],rt[to[j]],cnt);
			}
	dfs(n);
	int ans = 1; 
	E -= dis[1];
	//printf("%d\n",rt[1]);
	if(rt[1]) q.push(make_pair(dis[1]+t[rt[1]].cst,rt[1]));
	for(int now;!q.empty();){
		double val = q.top().first;
		//printf("%.3lf %.3lf\n",val,E);
		if(val > E + eps) break;
		ans ++ , E -= val;
		//printf("%.3lf\n",val);
		now = q.top().second , q.pop();
		for(int i=0;i<2;i++)
			if(t[now].ch[i])
				q.push(make_pair(val-t[now].cst+t[t[now].ch[i]].cst,t[now].ch[i]));
		if(rt[t[now].to]) 
			q.push(make_pair(val+t[rt[t[now].to]].cst,rt[t[now].to]));
	}
	printf("%d\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值