bzoj3047: Freda的传呼机&bzoj2125: 最短路

传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3047

http://www.lydsy.com/JudgeOnline/problem.php?id=2125

双倍经验题

题意很简单,求仙人掌两点间距离

各种情况讨论了我一天多...

在膜拜了一种新的建图方式后终于思路清晰了

思路:首先树上的很好搞,dis[x]+dis[y]-2*dis[lca(x,y)]

先用SPFA求出1号点到任意点的距离dis[x],这个待会求答案时要用

仙人掌也是一种植物,所以我们要把他变成一棵树

dfs时把每个简单环取出,每个环总有一个dfn最小的点,把它记做这个环的根

把所有环上的点向环根连边,记录它属于哪个环bel[x](根不记录,因为它还有可能是另一个环的节点),顺便求出环的长度

环边去掉,其他边不变,这就形成了一棵树



这棵树有什么用呢?

首先这棵树上的点没有变,只是边较原仙人掌有所变化

那么我们就可以求任意两点x,y,的lca了

接下来就是愉悦的分类讨论了

设dep[x]>dep[y]

分别记录x和y的祖先且是lca的儿子的点fx和fy


1.x和y中一个是lca

画图可知这个lca是不是原图的环都没有关系

直接return dis[x]-dis[y]


2.fx和fy都在同一个环上

即bel[x]!=0&&bel[x]==bel[y]

那么ans=dis[x]-dis[fx]+dis[y]-dis[fy]+d,d是fx,和fy在环上的距离

这个画图也能知道

d怎么求,可以在dfs时得到每个点的另一个距离rdis,这个rdis不是到1的最小距离,而是每个环都按一个方向走的距离

或者你可以在取环的时候顺便求出环上点到环根的一个方向的距离rdis,这种更好理解一些,虽然我脑子一抽用了上一种方法

这样就满足可减了,可以直接用abs(rdis[fx]-rdis[fy])得出fx和fy在环上的一段距离,用len[bel[fx]]-d可得另一种距离,取min即可


3.fx和fy有一个不在环上或x和y不在同一个环上

这个简单,画图即可 ans=dis[x]+dis[y]-dis[lca]*2


#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define abs(a) (a>0?a:(-(a)))
const int maxn=50010,maxm=100010,maxk=22;
using namespace std;
int n,m,Q,dis[maxn],head,tail,q[maxm+10],sta[maxn],top,rdis[maxn],rcnt,fa[maxn][maxk],dfn[maxn],last[maxn],bel[maxn],rlen[maxn],dep[maxn];
int pre[maxm],now[maxn],son[maxm],val[maxn],tot,tim;bool bo[maxn],del[maxm],vis[maxn];
void add(int a,int b,int c){pre[++tot]=now[a],now[a]=tot,son[tot]=b,val[tot]=c;}
void ins(int a,int b,int c){add(a,b,c),add(b,a,c);}
void spfa(){
	memset(dis,63,sizeof(dis));
	head=dis[1]=0,q[tail=1]=1,bo[1]=1;
	while (head<=tail){
		if (++head>maxm) head=1;
		int x=q[head];
		for (int y=now[x];y;y=pre[y]){
			if (dis[son[y]]>dis[x]+val[y]){
				dis[son[y]]=dis[x]+val[y];
				if (!bo[son[y]]){
					if (++tail>maxm) tail=1;
					q[tail]=son[y],bo[son[y]]=1;
				}
			}
		}
		bo[x]=0;
	}
}

void getring(int st,int ed,int id){
	del[id]=del[id^1]=1,rlen[++rcnt]+=val[id];
	for (int x=ed;x!=st;x=son[last[x]^1]){
		bel[x]=rcnt,del[last[x]]=del[last[x]^1]=1;
		ins(st,x,0),rlen[rcnt]+=val[last[x]];
	}
}

void dfs(int x){
	dfn[x]=++tim;
	for (int y=now[x];y;y=pre[y]) if (y!=(last[x]^1)&&(y<=(m*2+1))){
		if (!dfn[son[y]]) last[son[y]]=y,rdis[son[y]]=rdis[x]+val[y],dfs(son[y]);
		else if (dfn[son[y]]<dfn[x]) getring(son[y],x,y);
	}
}

void dfs2(int x){
	vis[x]=1;
	for (int i=1;i<=18;i++) fa[x][i]=fa[fa[x][i-1]][i-1];
	for (int y=now[x];y;y=pre[y]) if (!del[y]&&son[y]!=fa[x][0])
		dep[son[y]]=dep[x]+1,fa[son[y]][0]=x,dfs2(son[y]);
}

int query(int x,int y){
	if (dep[x]<dep[y]) swap(x,y);
	//printf("step1: %d %d\n",x,y);
	int a=x,b=y;
	for (int h=dep[x]-dep[y],i=18;h&&i>=0;i--) if (h&(1<<i)) h-=(1<<i),x=fa[x][i];
	//printf("step2: %d %d\n",x,y);
	if (x==y) return dis[a]-dis[b];//puts("cas1:链 "),
	for (int i=18;i>=0;i--)
		if (fa[x][i]!=fa[y][i])
			x=fa[x][i],y=fa[y][i];
	int lca=fa[x][0];
	//if (!x||!y||!lca) return 0;
	if (bel[x]&&bel[x]==bel[y]){
		//puts("cas2:相同的环  ");
		int d=abs(rdis[x]-rdis[y]);d=min(d,rlen[bel[x]]-d);
		return dis[a]+dis[b]-dis[x]-dis[y]+d;
	}
	//puts("cas3:不同的环或不是环  ");
	return dis[a]+dis[b]-2*dis[lca];
}

int main(){
	scanf("%d%d%d",&n,&m,&Q),tot=1;
	for (int i=1,x,y,z;i<=m;i++) scanf("%d%d%d",&x,&y,&z),ins(x,y,z);
	spfa(),last[1]=-1,dfs(1),dfs2(1);
	for (int i=1,x,y;i<=Q;i++) scanf("%d%d",&x,&y),printf("%d\n",query(x,y));
	return 0;
}

/*
13 18 3
1 2 5

2 14 3
2 9 5
14 9 2

9 15 4
9 10 6
15 10 3

10 11 7
11 12 6
12 13 5
13 10 4

2 3 2
3 4 3
4 5 2
5 6 3
6 7 2
7 8 3
8 3 2

15 12
13 2
15 12
*/


转载于:https://www.cnblogs.com/thythy/p/5493478.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值