lightoj 1101 - A Secret Mission(MST上的树链剖分)

All of you have heard about Evil Jack Diablo, the one who had stolen the whole problem set from the Good Judges last time. Once again he is making evil plans, but he does not know that Alice is on a secret mission. There will be several pairs of City of Evils on the way of Alice's mission.

There will be N evil cities (numbered by 1, 2, ..., N) connected by M bidirectional roads. There will be evil guards patrolling the roads. Since they are not much intelligent, the danger of travelling in each road is not the same. Alice is going to travel from city s to city t. You can safely assume that it's possible to travel from any city to another. John the legend programmer has estimated the danger of each road but since he is busy arranging contests, Alice is depending on you now. Danger of a path from city s to city t is defined as the maximum danger of any road on this path. As you are one of the most talented programmers, you will love to help Alice by finding the least dangerous paths to prevent Evil Jack from doing harms to the Judges.

Input

Input starts with an integer T (≤ 3), denoting the number of test cases.

Each case contains two integers N, M (2 ≤ N ≤ 50000, 1 ≤ M ≤ 105) denoting the number of cities and roads. Each of the next M lines contains three integers: xi, yi, di (1 ≤ xi, yi ≤ N, xi≠ yi, 1 ≤ di ≤ 104) - the cities connected by the ith road and its degree of danger. Next line contains an integer q (1 ≤ q ≤ 50000). Each of the next q lines contains two integers: si and ti (1 ≤ si, ti ≤ N, si ≠ ti).

Output

For each test case, print the case number first. Then for each query si ti, print the least dangerous path in a line.

Sample Input

Output for Sample Input

2

4 5

1 2 10

1 3 20

1 4 100

2 4 30

3 4 10

2

1 4

4 1

2 1

1 2 100

1

1 2

Case 1:

20

20

Case 2:

100



这题是给你n个点,m条边的无向图,然后有q个询问,每次询问两点之间最小的危险值,危险值就是一条路径上最大的那个边的权值。总体来说就是让你求任意两点之间的边权的最大值。

因为是路径上所有边的最大值,所以要尽量让这些边权值比较小,而且不能走环,所以图就转化成了最小生成树。

这样就变成在一棵树上求任意两点之间边权的最大值,这就是裸的树链剖分。下面来简单介绍一下树链剖分:

先介绍一下名词:

siz[v]表示以v为根的子树的节点数,dep[v]表示v的深度(根深度为1),top[v]表示v所在的链的顶端节点,fa[v]表示v的父亲,son[v]表示与v在同一重链上的v的儿子节点(姑且称为重儿子),id[v]表示v与其父亲节点的连边(姑且称为v的父边)在线段树中的位置。只要把这些东西求出来,就能用logn的时间完成原问题中的操作。

  重儿子:siz[u]为v的子节点中siz值最大的,那么u就是v的重儿子。
  轻儿子:v的其它子节点。
  重边:点v与其重儿子的连边。
  轻边:点v与其轻儿子的连边。
  重链:由重边连成的路径。
  轻链:轻边。

先通过两个dfs把上述数组的值都处理出来。

然后就是区间操作的过程了:

记f1 = top[u],f2 = top[v]。
  当f1 <> f2时:不妨设dep[f1] >= dep[f2],那么就更新(查询)u到f1的父边的权值(logn),并使u = fa[f1]。
  当f1 = f2时:u与v在同一条重链上,若u与v不是同一点,就更新(查询)u到v路径上的边的权值(logn),否则修改(查询)完成;
  重复上述过程,直到修改(查询)完成。

如图所示,较粗的为重边,较细的为轻边。节点编号旁边有个红色点的表明该节点是其所在链的顶端节点。边旁的蓝色数字表示该边在线段树中的位置。图中1-4-9-13-14为一条重链。

  当要修改11到10的路径时。
  第一次迭代:u = 11,v = 10,f1 = 2,f2 = 10。此时dep[f1] < dep[f2],因此修改线段树中的5号点,v = 4, f2 = 1;
  第二次迭代:dep[f1] > dep[f2],修改线段树中10--11号点。u = 2,f1 = 2;
  第三次迭代:dep[f1] > dep[f2],修改线段树中9号点。u = 1,f1 = 1;
  第四次迭代:f1 = f2且u = v,修改结束。

#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int N = 50010;
int dep[N],siz[N],fa[N],id[N],son[N],top[N];
int num,cnt;
vector<int> v[N];
struct node
{
    int x,y,z;
}t[N];

void dfs1(int x,int f,int d)
{
    dep[x] = d;
    siz[x] = 1;
    son[x] = 0;
    fa[x] = f;
    for(int i=0;i<v[x].size();i++)
    {
        int xx = v[x][i];
        if(xx == f)
            continue;
        dfs1(xx,x,d+1);
        siz[x] += siz[xx];
        if(siz[son[x]] < siz[xx])
            son[x] = xx;
    }
}
void dfs2(int x,int tp)//确定结点在线段树中的位置
{
    top[x] = tp;
    id[x] = ++num;
    if(son[x])
        dfs2(son[x],tp);
    for(int i=0;i<v[x].size();i++)
    {
        int xx = v[x][i];
        if(xx == fa[x] || xx == son[x])
            continue;
        dfs2(xx,xx);
    }
}

int sum[N*4];
void PushUp(int rt)
{
    sum[rt] = max(sum[rt*2],sum[rt*2+1]);
}

void update(int p, int add, int l, int r, int rt)
{
    if (l == r)
    {
        sum[rt] = add;
        return;
    }
    int m = (l + r) / 2;
    if (p <= m)
        update(p, add, l, m, rt*2);
    else
        update(p, add, m + 1, r, rt*2+1);

    PushUp(rt);
}

int query(int ll, int rr, int l, int r, int rt)//查询线段树
{
    if(ll > r || rr < l)
        return 0;
    if (ll <= l && rr >= r) return sum[rt];
    //PushDown(l,r,rt);
    int m = (l + r) / 2;
    int ret = 0;
    if (ll <= m)
        ret = max(ret,query(ll, rr, l, m, rt*2));
    if (rr > m)
        ret = max(ret,query(ll, rr, m + 1, r, rt*2+1));
    return ret;
}

int solve(int va, int vb)
{
    int f1 = top[va], f2 = top[vb], tmp = 0;
    while(f1 != f2)
    {
        if(dep[f1] < dep[f2])
        {
            swap(f1, f2);
            swap(va, vb);
        }
        tmp = max(tmp, query(id[f1], id[va],1,num,1));
        va = fa[f1], f1 = top[va];
    }
    if(va == vb)
        return tmp;
    if(dep[va] > dep[vb])
        swap(va, vb);
    return max(tmp,query(id[son[va]],id[vb],1,num,1));
}
//-----以下是最小生成树过程----
int f[N];
struct tree
{
    int u,v,w;
}e[100010];
bool cmp(struct tree a, struct tree b)
{
    return a.w<b.w;
}
int find(int x)
{
    if(x == f[x])
        return f[x];
    else
        return f[x] = find(f[x]);
}
void MST(int n,int m)
{
    int i,x,y;
    for(i=1;i<=n;i++)
        f[i]=i;
    for(i=0;i<m;i++)
        scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
    sort(e,e+m,cmp);
    for(i=0;i<m;i++)
    {
        x = find(e[i].u);
        y = find(e[i].v);
        if(x!=y)
        {
            t[cnt].x = e[i].u;
            t[cnt].y = e[i].v;
            t[cnt++].z = e[i].w;
            v[e[i].u].push_back(e[i].v);
            v[e[i].v].push_back(e[i].u);
            f[x] = y;
        }
    }
}
//-----以上是最小生成树过程------
void init()
{
    num = 0;
    dfs1(1,0,1);
    dfs2(1,1);
    for(int i=0;i<cnt;i++)
    {
        int pos = max(id[t[i].x],id[t[i].y]);
        update(pos,t[i].z,1,num,1);
    }
}
int main(void)
{
    int T,n,m,i,j,q;
    scanf("%d",&T);
    int cas = 1;
    while(T--)
    {
        memset(sum,0,sizeof(sum));
        scanf("%d%d",&n,&m);
        for(i=1;i<=n;i++)
            v[i].clear();
        cnt = 0;
        MST(n,m);
        init();
        scanf("%d",&q);
        printf("Case %d:\n",cas++);
        while(q--)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            printf("%d\n",solve(x,y));
        }
    }

    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值