zoj 3649 树上的倍增法

Social Net

Time Limit: 5 Seconds       Memory Limit: 65536 KB

There are n individuals(2 <= n <= 30000). Everyone has one or more friends. And everyone can contact all people by friend-relation. If two persons aren't friends, they also can contact by their friends. Each pair of friends have a friendship value ai(1 <= ai <= 50000).

Firstly, you will relieve some friend-relation. The rest of the friend-relation is the social net. The net is unique in all test cases. In this net, everyone can contact all people by rest friend-relation. The net has a minimum number of friend-relation. And the net has maximum sum of friendship value. We want to get the maximum sum.

Secondly, everyone has an angry value bi(1 <= bi <= 100000). We have q operations(1 <= q <= 30000): Person X wants to contact person Y, this operation merely has one sequence which describes the process. The sequence consists of persons' angry value. The persons are on the process.

We suppose the sequence is c1c2c3, ... ,ci. Here ci means the angry value of the ith people in the sequence.

We attempt to find the maximum ck-cj (ck >= cjj <= k).

Example:

The sequence is 3(X), 4, 5, 6, 7, 5, 9, 4, 11(Y). The maximum ck-cj is 11-3=8.

The sequence is 3(X), 4, 5, 6, 7, 5, 9, 2, 11(Y). The maximum ck-cj is 11-2=9.

The sequence is 3(X), 10, 2, 5(Y). The maximum ck-cj is 10-3=7.

Input

The input contains multiple test cases. Each test case begins with a line containing a single integer n. The following line contains n integers bi.

The subsequent line describe the number of relations m(n <= m <= 50000). The next m lines contain the information about relations: xyai. Their friendship value is ai.

Afterward gives q. The next q lines contain the operations: xy. person X wants to contact person Y.

Output

For each case, print maximum sum of friendship value of the net on the first line.

The next q lines contain the answers of every operations.

Sample Input
6
3 5 1 7 3 5
7
1 2 5
1 3 6
2 4 7
2 5 8
3 6 9
4 5 1
5 6 2
5
6 1
6 2
6 3
6 4
6 5
Sample Output
35
2
4
0
6
4

第一问求最小生成树模板。第二问,每个节点有个ci值,查询树上节点u到v路径上(经过LCA)的点ci-cj(i>j)的最大值。

第二问麻烦就在于是树上的查询。先考虑简单的情形,假如生成的树是一条链,就变成rmq问题了,查询区间[l,r] ci-cj(i>=j)的最大值, 最大值要么是i<=m在[l,m]区间上,要么是j>m在[m+1,r]上,要么就是i>m,j<=m,在中间上等于右边区间最大值减去左边区间的最小值。所以假如用线段树之类rmq算法,线段树节点应该维护三个值,节点所代表的md[i]-ci-cj最大值,mi[i]-节点区间的最小值,mx[i]-节点区间的最大值。

但是线段树并不能直接用在树上,而还有一个rmq算法就是st算法,其原理就是倍增法,利用了二进制的思想。其实倍增法求树的最近公共祖先也类似于区间查询,知道了[l,m]的祖先(par[i][k])和[m+1,r]的祖先(par[par[i][k]][k]),那么就知道了[l,r]的祖先(par[i][k+1]),par[i][k]代表第i个节点的第2^k个祖先。

所以这道题需要维护的就是md[i][k],mdd[i],mi[i][k],mx[i][k],分别代表第i个节点开始到其第2^k个祖先这段序列的最大ci-cj值,最大cj-ci值,最小ci值,最大ci值。这里md[i]和mdd[i],一个是从下往上走,一个是从上往下走,因为这道题需要考虑走的方向,比如查询(u,v)和(v,u)方向是刚好相反的,所以要维护两个方向的。

但是树上的倍增法和st算法一样,支持区间查询操作但无法支持修改操作。其中对应的区间查询应该满足知道[l,m]和[m+1,r]的值,就能O(1)求的[l,r]对应的值。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>
using namespace std;
#define maxn 30005
#define maxv 16
#define inf 0x3f3f3f3f
typedef pair<int, int> p;
struct edge;
vector<edge> g[maxn];
int md[maxn][maxv], mdd[maxn][maxv], mi[maxn][maxv], mx[maxn][maxv];
int par[maxn][maxv];
int c[maxn], d[maxn];
int n,m,M;

struct edge
{
    int u,v,w;
    edge(){}
    edge(int uu, int vv, int ww){ u=uu;v=vv;w=ww;}
    bool operator <(const edge &a) const
    {
        return w>a.w;
    }
};
edge e[2*maxn];
int father[maxn]; //并查集用于kruskal
int find(int u)
{
    return u==father[u]?u:(father[u]=find(father[u]));
}

void unite(int u, int v)
{
    u=find(u);
    v=find(v);
    father[v]=u;
}

int vis[maxn];
void dfs(int u, int p, int dep)
{
    vis[u]=1;
    d[u]=dep;
    par[u][0]=p;
    if(p==-1){
        md[u][0]=0;
        mdd[u][0]=0;
        mi[u][0]=mx[u][0]=c[u];
    }
    else{
        md[u][0]= max(0, c[p]-c[u]);
        mdd[u][0]=max(0, c[u]-c[p]);
        mi[u][0]=min(c[p], c[u]);
        mx[u][0]=max(c[p], c[u]);
    }
    int v,w;
    for(int i=0; i<g[u].size(); i++){
        v=g[u][i].v, w=g[u][i].w;
        if(vis[v]) continue;
        dfs(v, u, dep+1);
    }
}

void init()
{
    for(int i=0; i<=n; i++) father[i]=i, g[i].clear();
    for(int i=1; i<=n; i++) scanf("%d", c+i);
    scanf("%d", &m);
    for(int i=0; i<m; i++)
        scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);
    sort(e, e+m);

    int cnt=0;
    int res=0;
    for(int i=0; i<m && cnt<n-1; i++){  //kruskal最小生成树
        int u=e[i].u, v=e[i].v, w=e[i].w;
        if(find(u)==find(v)) continue;
        g[u].push_back(edge(u, v, w));
        g[v].push_back(edge(v, u, w));
        unite(u, v);
        res+=w;
        cnt++;
    }
    printf("%d\n", res);

    memset(vis, 0, sizeof(vis));
    for(int i=1; i<=n; i++)
        if(!vis[i])
        dfs(i,-1, 0);

    M=0;
    for(int i=1; i<=n; i++)
        M=max(d[i], M);
    M=ceil(log(M)/log(2));
    for(int v=1; v<=M; v++)
    for(int i=1; i<=n; i++){//初始化
       if(par[i][v-1]==-1){
            par[i][v]=-1;
            md[i][v]=md[i][v-1];
            mdd[i][v]=mdd[i][v-1];
            mx[i][v]=mx[i][v-1];
            mi[i][v]=mx[i][v-1];
       }
       else{
            int p=par[i][v-1];
            par[i][v]=par[p][v-1];
            mx[i][v]=max(mx[i][v-1], mx[p][v-1]);
            mi[i][v]=min(mi[i][v-1], mi[p][v-1]);
            md[i][v]=max(max(md[i][v-1], md[p][v-1]) ,mx[p][v-1]-mi[i][v-1]);
            mdd[i][v]=max(max(mdd[i][v-1], mdd[p][v-1]), mx[i][v-1]-mi[p][v-1]);
       }
    }
}

int lca(int u, int v)//求出最近公共祖先
{
    if(d[u]>d[v]) swap(u,v);

    for(int k=0; k<=M && d[v]!=d[u]; k++){
        if( (d[v]-d[u])>>k&1)
        v=par[v][k];

    }

    if(u==v) return u;
    for(int k= M; k>=0; k--){
        if(par[u][k] != par[v][k])
            u=par[u][k], v=par[v][k];
    }

    return par[u][0];
}

//md代表从下往上区间的最大差值,mdd代表从上往下的区间最大差值
//u,lca,v。假如u在v的左边。
//则需要查询(u,lca)的最大md值,和(lca,v)的最大mdd值,和((lca,v)的最大值-(u,lca)的最小值)。
p getmd(int u, int v) 
{
    if(u==v) return p(0, inf);
    int res=0, mini=c[u];
    for(int k=0; k<=M && d[v]!=d[u]; k++){
        if( (d[u]-d[v])>>k &1){ //利用二进制原理,比如往上走3步==00000011,等走2^0+2^1步
            res=max(md[u][k], res);
            res=max(res, mx[u][k]-mini);
            mini=min(mi[u][k], mini);
            u=par[u][k];
        }
    }

    return p(res, mini);  //返回最大md值,和区间最小值
}

p getmdd(int u, int v)
{
    if(u==v) return p(0, -inf);

    int res=0, maxi=c[u];
    for(int k=0; k<=M && d[v]!=d[u]; k++){
        if( (d[u]-d[v])>>k &1){
            res=max(mdd[u][k], res);
            res=max(res, maxi-mi[u][k]);
            maxi=max(maxi, mx[u][k]);
            u=par[u][k];
        }
    }

    return p(res, maxi);
}

int main()
{
    while(scanf("%d", &n)==1){
        init();
        int q;
        scanf("%d", &q);
        int u,v;
        while(q--){
            scanf("%d%d", &u, &v);
            int anc=lca(u,v);
            p t1=getmd(u, anc);
            p t2=getmdd(v, anc);
            int ans=0;
            
            ans=max(t1.first, t2.first);
            ans=max(ans, t2.second-t1.second);
            printf("%d\n", ans);

        }
    }
    return 0;
}


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值