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
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模板要改进一下
命名要有区别性,不能太相似了,不然会搞错,主要是数组与函数变量