team2012-E1-mysol-0019

从 Trac 迁移的文章

这是从旧校内 Wiki 迁移的文章,可能存在一些样式问题,您可以向 memset0 反馈。

原文章内容如下:

{{{

/* 解题报告 //

给出 n 个点,m 条边,题目要求是先求一个最大生成树
在生成树上,给出 q 次查询,每次询问一条从 x 到 y 的路径
然后求 c[j] - c[i] 的最大值,其中 i 和 j 均是路径上的点
且对于这条路径而言,j 出现的位置不会比 i 出现的位置更靠前

最大生成树的做法就不用说了,大家都会吧?来说说怎么处理查询
首先,你必须要会用倍增法来求 Kaguya's Game 那题的 LCA 查询
如果不会的话,可以去看看那题我的解题报告

然后要维护一些数据,以便之后的查询,要维护的值有这些:
1. 从结点 x 出发,向根走 2 ^ i 步到达的祖先是谁
2. 从结点 x 出发,向根走 2 ^ i 步的这条路径上(含路径起点,不含终点)
  2.1. 最小值是多少(此程序里的 lo 数组)
  2.2. 最大值是多少(此程序里的 hi 数组)
  2.3. 对于路径中间任意的两点 i 和 j,在满足条件 i <= j 的情况下
    2.3.1. c[j] - c[i] 的最大值为多少(此程序里的 up 数组)
    2.3.2. c[i] - c[j] 的最大值为多少(此程序里的 dn 数组)

由于都是『向根走 2 ^ i 步』的那种类型的数据,所以可以倍增地维护
具体维护方法可以参考 init() 函数

对于查询一条路径 x 到 y,不妨令 w = lca(x, y)
可以发现,x 到 y 这条路径可以被分解成一些长度为 2 ^ i 的段
段的总数是 O(logN) 级别的,最优答案可能来自两个部分:
1. 每段内部
  1.1. 对于 x 到 w 的路径,答案来自 up 数组
  1.2. 对于 w 到 y 的路径,答案来自 dn 数组(因为路径是反向的)
2. 段与段之间
   把每段的最大值和最小值各自拿出来放在两个数组 l 和 h 内
   l[i] = 前 i 个数中的最小值,h[i] = 从 i 开始直到末尾这些数中的最大值
   再线性地扫一遍,就可以得到答案了

// By 猛犸也钻地 @ 2012.07.26 */
}}}

{{{
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;

typedef pair<int,int> PII;
typedef pair<int,PII> TPL;
const int INF = 1000000007;

int sz[50000],p[50000],c[50000],lv[50000];
int fa[50000][16],lo[50000][16],hi[50000][16];
int up[50000][16],dn[50000][16];
vector<int> e[50000];
TPL u[50000];

int root(int x){
    return p[x]!=x?p[x]=root(p[x]):x;
}

bool join(int x, int y){
    x=root(x),y=root(y);
    if(x==y) return false;
    if(sz[x]<=sz[y]) sz[p[x]=y]+=sz[x];
                else sz[p[y]=x]+=sz[y];
    return true;
}

void init(int x, int src){
    up[x][0]=dn[x][0]=-INF;
    lo[x][0]=hi[x][0]=c[x];
    fa[x][0]=src;
    lv[x]=lv[src]+1;
    for(int i=1;i<16;i++){
        int r=fa[x][i-1];
        fa[x][i]=fa[r][i-1];
        lo[x][i]=min(lo[r][i-1],lo[x][i-1]);
        hi[x][i]=max(hi[r][i-1],hi[x][i-1]);
        up[x][i]=max(hi[r][i-1]-lo[x][i-1],max(up[r][i-1],up[x][i-1]));
        dn[x][i]=max(hi[x][i-1]-lo[r][i-1],max(dn[r][i-1],dn[x][i-1]));
    }
}

void make_tree(int x, int src){
    init(x,src);
    for(size_t i=0;i<e[x].size();i++)
        if(e[x][i]!=src) make_tree(e[x][i],x);
}

int lca(int x, int y){
    int c=lv[x]-lv[y];
    if(c>0) for(int i=0;i<16;i++) if(+c&1<<i) x=fa[x][i];
    if(c<0) for(int i=0;i<16;i++) if(-c&1<<i) y=fa[y][i];
    if(x==y) return x;
    for(int i=15;~i;i--)
        if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}

int gao(int x, int y, int w){
    int p=lv[x]-lv[w];
    int q=lv[y]-lv[w],ret=0;
    vector<int> l,h,L,H;
    for(int i=0;i<16;i++){
        if(p&1<<i){
            ret=max(ret,up[x][i]);
            l.push_back(lo[x][i]);
            h.push_back(hi[x][i]);
            x=fa[x][i];
        }
        if(q&1<<i){
            ret=max(ret,dn[y][i]);
            L.push_back(lo[y][i]);
            H.push_back(hi[y][i]);
            y=fa[y][i];
        }
    }
    l.push_back(c[w]); l.insert(l.end(),L.rbegin(),L.rend());
    h.push_back(c[w]); h.insert(h.end(),H.rbegin(),H.rend());
    for(size_t i=h.size()-2;~i;i--) h[i]=max(h[i],h[i+1]);
    for(size_t i=1;i!=l.size();i++) l[i]=min(l[i],l[i-1]),ret=max(ret,h[i]-l[i-1]);
    return ret;
}

int main(){
    int n,m,x,y,w;
    while(scanf("%d",&n)==1){
        for(int i=0;i<n;i++){
            scanf("%d",c+i);
            sz[p[i]=i]=1;
        }
        scanf("%d",&m);
        for(int i=0;i<m;i++){
            scanf("%d%d%d",&x,&y,&w);
            u[i]=TPL(w,PII(x-1,y-1));
        }
        long long ans=0;
        sort(u,u+m);
        for(int i=m-1,cnt=1;~i;i--){
            x=u[i].second.first;
            y=u[i].second.second;
            if(!join(x,y)) continue;
            ans+=u[i].first;
            e[x].push_back(y);
            e[y].push_back(x);
            if(++cnt==n) break;
        }
        printf("%lld\n",ans);
        make_tree(0,0);
        scanf("%d",&m);
        for(int i=0;i<m;i++){
            scanf("%d%d",&x,&y);
            w=lca(--x,--y);
            printf("%d\n",gao(x,y,w));
        }
        for(int i=0;i<n;i++) e[i].clear();
    }
}
}}}
/* 解题报告 //
给出 n 个点,m 条边,题目要求是先求一个最大生成树
在生成树上,给出 q 次查询,每次询问一条从 x 到 y 的路径
然后求 c[j] - c[i] 的最大值,其中 i 和 j 均是路径上的点
且对于这条路径而言,j 出现的位置不会比 i 出现的位置更靠前
最大生成树的做法就不用说了,大家都会吧?来说说怎么处理查询
首先,你必须要会用倍增法来求 Kaguya's Game 那题的 LCA 查询
如果不会的话,可以去看看那题我的解题报告
然后要维护一些数据,以便之后的查询,要维护的值有这些:
1. 从结点 x 出发,向根走 2 ^ i 步到达的祖先是谁
2. 从结点 x 出发,向根走 2 ^ i 步的这条路径上(含路径起点,不含终点)
  2.1. 最小值是多少(此程序里的 lo 数组)
  2.2. 最大值是多少(此程序里的 hi 数组)
  2.3. 对于路径中间任意的两点 i 和 j,在满足条件 i <= j 的情况下
    2.3.1. c[j] - c[i] 的最大值为多少(此程序里的 up 数组)
    2.3.2. c[i] - c[j] 的最大值为多少(此程序里的 dn 数组)
由于都是『向根走 2 ^ i 步』的那种类型的数据,所以可以倍增地维护
具体维护方法可以参考 init() 函数
对于查询一条路径 x 到 y,不妨令 w = lca(x, y)
可以发现,x 到 y 这条路径可以被分解成一些长度为 2 ^ i 的段
段的总数是 O(logN) 级别的,最优答案可能来自两个部分:
1. 每段内部
  1.1. 对于 x 到 w 的路径,答案来自 up 数组
  1.2. 对于 w 到 y 的路径,答案来自 dn 数组(因为路径是反向的)
2. 段与段之间
   把每段的最大值和最小值各自拿出来放在两个数组 l 和 h 内
   l[i] = 前 i 个数中的最小值,h[i] = 从 i 开始直到末尾这些数中的最大值
   再线性地扫一遍,就可以得到答案了
// By 猛犸也钻地 @ 2012.07.26 */
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<int,int> PII;
typedef pair<int,PII> TPL;
const int INF = 1000000007;
int sz[50000],p[50000],c[50000],lv[50000];
int fa[50000][16],lo[50000][16],hi[50000][16];
int up[50000][16],dn[50000][16];
vector<int> e[50000];
TPL u[50000];
int root(int x){
    return p[x]!=x?p[x]=root(p[x]):x;
}
bool join(int x, int y){
    x=root(x),y=root(y);
    if(x==y) return false;
    if(sz[x]<=sz[y]) sz[p[x]=y]+=sz[x];
                else sz[p[y]=x]+=sz[y];
    return true;
}
void init(int x, int src){
    up[x][0]=dn[x][0]=-INF;
    lo[x][0]=hi[x][0]=c[x];
    fa[x][0]=src;
    lv[x]=lv[src]+1;
    for(int i=1;i<16;i++){
        int r=fa[x][i-1];
        fa[x][i]=fa[r][i-1];
        lo[x][i]=min(lo[r][i-1],lo[x][i-1]);
        hi[x][i]=max(hi[r][i-1],hi[x][i-1]);
        up[x][i]=max(hi[r][i-1]-lo[x][i-1],max(up[r][i-1],up[x][i-1]));
        dn[x][i]=max(hi[x][i-1]-lo[r][i-1],max(dn[r][i-1],dn[x][i-1]));
    }
}
void make_tree(int x, int src){
    init(x,src);
    for(size_t i=0;i<e[x].size();i++)
        if(e[x][i]!=src) make_tree(e[x][i],x);
}
int lca(int x, int y){
    int c=lv[x]-lv[y];
    if(c>0) for(int i=0;i<16;i++) if(+c&1<<i) x=fa[x][i];
    if(c<0) for(int i=0;i<16;i++) if(-c&1<<i) y=fa[y][i];
    if(x==y) return x;
    for(int i=15;~i;i--)
        if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}
int gao(int x, int y, int w){
    int p=lv[x]-lv[w];
    int q=lv[y]-lv[w],ret=0;
    vector<int> l,h,L,H;
    for(int i=0;i<16;i++){
        if(p&1<<i){
            ret=max(ret,up[x][i]);
            l.push_back(lo[x][i]);
            h.push_back(hi[x][i]);
            x=fa[x][i];
        }
        if(q&1<<i){
            ret=max(ret,dn[y][i]);
            L.push_back(lo[y][i]);
            H.push_back(hi[y][i]);
            y=fa[y][i];
        }
    }
    l.push_back(c[w]); l.insert(l.end(),L.rbegin(),L.rend());
    h.push_back(c[w]); h.insert(h.end(),H.rbegin(),H.rend());
    for(size_t i=h.size()-2;~i;i--) h[i]=max(h[i],h[i+1]);
    for(size_t i=1;i!=l.size();i++) l[i]=min(l[i],l[i-1]),ret=max(ret,h[i]-l[i-1]);
    return ret;
}
int main(){
    int n,m,x,y,w;
    while(scanf("%d",&n)==1){
        for(int i=0;i<n;i++){
            scanf("%d",c+i);
            sz[p[i]=i]=1;
        }
        scanf("%d",&m);
        for(int i=0;i<m;i++){
            scanf("%d%d%d",&x,&y,&w);
            u[i]=TPL(w,PII(x-1,y-1));
        }
        long long ans=0;
        sort(u,u+m);
        for(int i=m-1,cnt=1;~i;i--){
            x=u[i].second.first;
            y=u[i].second.second;
            if(!join(x,y)) continue;
            ans+=u[i].first;
            e[x].push_back(y);
            e[y].push_back(x);
            if(++cnt==n) break;
        }
        printf("%lld\n",ans);
        make_tree(0,0);
        scanf("%d",&m);
        for(int i=0;i<m;i++){
            scanf("%d%d",&x,&y);
            w=lca(--x,--y);
            printf("%d\n",gao(x,y,w));
        }
        for(int i=0;i<n;i++) e[i].clear();
    }
}