[bzoj3694]最短路 树链剖分

3694: 最短路

Time Limit: 5 Sec   Memory Limit: 256 MB
[ Submit][ Status][ Discuss]

Description

给出一个n个点m条边的无向图,n个点的编号从1~n,定义源点为1。定义最短路树如下:从源点1经过边集T到任意一点i有且仅有一条路径,且这条路径是整个图1到i的最短路径,边集T构成最短路树。  给出最短路树,求对于除了源点1外的每个点i,求最短路,要求不经过给出的最短路树上的1到i的路径的最后一条边。
 

Input

第一行包含两个数n和m,表示图中有n个点和m条边。
接下来m行,每行有四个数ai,bi,li,ti,表示图中第i条边连接ai和bi权值为li,ti为1表示这条边是最短路树上的边,ti为0表示不是最短路树上的边。

Output

输出n-1个数,第i个数表示从1到i+1的要求的最短路。无法到达输出-1。

Sample Input

5 9
3 1 3 1
1 4 2 1
2 1 6 0
2 3 4 0
5 2 3 0
3 2 2 1
5 3 1 1
3 5 2 0
4 5 4 0

Sample Output

6 7 8 5

HINT

 对于100%的数据,n≤4000,m≤100000,1≤li≤100000

Source

orz
首先把最短路径树建出来
把不在这棵树上的边存下来
然后对答案进行更新
具体是:
开始都是inf的距离
对于一条边u->v,长度为w,设t=lca(u,v),对于t-v链上所有点x,可通过root -> t -> u -> v -> x 到达x,距离为dis[u]+w+dis[v]-dis[x]
然后依次更新,查询就直接查单点就可以了
顺带一提,此题是双倍经验题,但我不能刷经验啊,以后做是可以的
#include<iostream>
#include<cstring>
#include<cstdio>
#define inf 0x7fffffff
const int N = 100000 + 5;
using namespace std;
int cnt=1,n,m,sz;
int pos[N],bel[N],last[N],dep[N],dis[N],siz[N],anc[N][20],u[N],v[N],w[N];
int ls[N*4],rs[N*4],mx[N*4],sum[N*4],l[N*4],r[N*4],va[N*4],type[N*4],root,idd,cnt1=1;
struct Edge{int to,next,v;}e[N<<1];
void insert(int u,int v, int w ){
	e[++cnt].to = v; e[cnt].next = last[u]; e[cnt].v = w; last[u] = cnt;
	e[++cnt].to = u; e[cnt].next = last[v]; e[cnt].v = w; last[v] = cnt;
}
void dfs1( int x, int f ){
	siz[x] = 1;
	for( int p = 1; p <= 11; p++ )
		if( (1<<p) <= dep[x] ) anc[x][p] = anc[anc[x][p-1]][p-1];
		else break;
	for( int i = last[x]; i; i = e[i].next )
		if( e[i].to != f ){
			dep[e[i].to] = dep[x]+1;
			dis[e[i].to] = dis[x]+e[i].v;
			anc[e[i].to][0] = x;
			dfs1(e[i].to,x);
			siz[x] += siz[e[i].to];
		}
}
void dfs2( int x, int chain ){
	int k = 0; bel[x] = chain; pos[x] = ++sz;
	for( int i = last[x]; i; i = e[i].next )
		if( dep[e[i].to] > dep[x] )
			if( siz[e[i].to] > siz[k] ) k = e[i].to;
	if( !k ) return; dfs2(k,chain);
	for( int i = last[x]; i; i = e[i].next )
		if( dep[e[i].to] > dep[x] && e[i].to != k ) dfs2(e[i].to,e[i].to);
}
int lca( int x, int y ){
	if( dep[x] < dep[y] ) swap(x,y);
	int t = dep[x]-dep[y];
	for( int p = 0; p <= 11; p++ ) if( (1<<p)&t ) x = anc[x][p];
	if( x == y ) return x;
	for( int p = 11; p >= 0; p-- ) if( anc[x][p] != anc[y][p] ) x = anc[x][p], y = anc[y][p];
	return anc[x][0];
}
void build( int &k, int L, int R ){
	k = ++idd; l[k] = L; r[k] = R; type[k] = inf;
	if( L == R ){ va[k] = inf; return; }
	int mid = (L+R)>>1;
	build( ls[k], L, mid ); build( rs[k], mid+1, R );
}
void pushdown( int k ){
	if( l[k] == r[k] || type[k] == inf ) return;
	type[ls[k]] = min(type[k],type[ls[k]]);
	type[rs[k]] = min(type[k],type[rs[k]]);
	if( l[ls[k]] == r[ls[k]] ) va[ls[k]] = min( va[ls[k]], type[k] );
	if( l[rs[k]] == r[rs[k]] ) va[rs[k]] = min( va[rs[k]], type[k] );
	type[k] = inf;
}
void modify( int k, int x, int y, int v ){
	pushdown(k);
	if( l[k] == x && r[k] == y ){
		type[k] = min(type[k],v);
		if( l[k] == r[k] ) va[k] = min(v,va[k]);
		return;
	}
	int mid = (l[k]+r[k])>>1;
	if( y <= mid ) modify( ls[k], x, y, v );
	else if( x >  mid ) modify( rs[k], x, y, v );
	else{ modify(ls[k],x,mid,v); modify(rs[k],mid+1,y,v); }
}
int query( int k, int x ){
	pushdown(k);
	if( l[k] == r[k] ) return va[k];
	int mid = (l[k]+r[k])>>1;
	if( x <= mid ) return query(ls[k],x);
	else return query(rs[k],x);
}
void solve_modify( int x, int f, int v ){
	while( bel[x] != bel[f] ){
		modify( root, pos[bel[x]], pos[x], v );
		x = anc[bel[x]][0];
	}
	if( x != f ) modify( root, pos[f]+1, pos[x], v );
}
int main(){
	scanf("%d%d", &n, &m);
	for( int i = 1,flag; i <= m; i++ ){
		scanf("%d%d%d%d", &u[cnt1], &v[cnt1], &w[cnt1], &flag);
		if( !flag ) cnt1++; else insert(u[cnt1],v[cnt1],w[cnt1]);
	}
	dfs1(1,0); dfs2(1,1); build( root, 1, n );
	for( int i = 1; i < cnt1; i++ ){
		int t = lca(u[i],v[i]);
		solve_modify( u[i], t, dis[u[i]]+dis[v[i]]+w[i] );
		solve_modify( v[i], t, dis[u[i]]+dis[v[i]]+w[i] );
	}
	for( int i = 2; i <= n; i++ ){
		int d = query( root, pos[i] );
		if( d != inf ) printf("%d ", d-dis[i]);
	    else printf("-1 ");
	}
	return 0;
}
总结
树链剖分和lca模板要改进一下
命名要有区别性,不能太相似了,不然会搞错,主要是数组与函数变量

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值