zoj 3649 lca与倍增dp

参考:http://www.xuebuyuan.com/609502.html

先说题意:

给出一幅图,求最大生成树,并在这棵树上进行查询操作:给出两个结点编号x和y,求从x到y的路径上,由每个结点的权值构成的序列中的极差大小——要求,被减数要在减数的后面,即形成序列{a1,a2…aj …ak…an},求ak-aj (k>=j)的最大值。

求路径,显然用到lca。

太孤陋寡闻,才知道原来倍增dp能用来求LCA。

用p[u][i]表示结点u的第1<< i 个祖先结点,则有递推如下:

for(int i=0;i<POW;i++)  p[u][i]=p[p[u][i-1]][i-1]。

在对图dfs的时候即完成递推。

要想求两个结点的lca,首先使得两结点高度相同,若二者的父亲结点不同,则一直向上查找。dep数组表示结点的深度。

int LCA(int a,int b){

if(dep[a]>dep[b]) swap(a,b);

if(dep[a]<dep[b]){

//这一部分使得dep[a]==dep[b]

int tmp=dep[b]-dep[a];

for(int i=0;i<POW;i++) if(tmp&(1<<i))

//这里从POW-1到0来遍历也是一样的

b=p[b][i];

}

if(a!=b){

for(int i=POW-1;i>=0;i--) if(p[a][i]!=p[b][i])

a=p[a][i],b=p[b][i];

a=p[a][0],b=p[b][0];

}

return a;

}

如此即返回结点的lca。

用倍增遍历的思路:

因为一段路被二进制分成了一截一截,或者说路径长度被用二进制表示了出来。而两个结点的深度差即为“路径长度”,所以只要tmp&(1<<i),则表示这是“路径”的其中一个结点,以此类推,从而得到两个深度相同的结点。

有了这个基础之后,用相同的方式构建——

mx数组,mx[u][i]表示从u到其第1<<i个祖先结点路径上的最大值

mn数组,mn[u][i]表示从u到其第1<<i个祖先结点路径上的最小值

dp数组,dp[u][i],表示从u到其第1<<i个祖先结点路径上的最大差值

dp2数组,dp2[u][i],表示从其第1<<i个祖先结点到u路径上的最大差值

构建好后是查询部分。给出结点x和y,获得lca。

则路径被分成两段—— x->lca->y。则有三种可能性:

x到lca上的最大差值;lca到y上的最大差值;x到y上的最大差值(即lca到y的最大值减去x到lca的最小值)。比较一下即可。

这题真心涨姿势。代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=3e4+10,M=N<<1,POW=16,inf=21e8;
int mx[N][POW],mn[N][POW],p[N][POW],dp[N][POW],dp2[N][POW];
int head[N],nxt[M],to[M],cnt,val[N],vis[N],dep[N];
int n,m,q,fa[N];
struct Edge{
    int u,v,w;
    bool operator < (const Edge e) const{
        return w>e.w;
    }
}E[M];
void ini(int n){
    memset(head,-1,sizeof(head));
    cnt=0;
    memset(vis,0,sizeof(vis));
    fill(p[0],p[n+1],0);
    fill(mx[0],mx[n+1],-inf);
    fill(mn[0],mn[n+1],inf);
    fill(dp[0],dp[n+1],-inf);
    fill(dp2[0],dp2[n+1],-inf);
    dep[0]=0;
}
int find_(int x){
    return x==fa[x]?x:fa[x]=find_(fa[x]);
}
void addedge(int u,int v){
    to[cnt]=v;
    nxt[cnt]=head[u];
    head[u]=cnt++;
}
int Kruskal(){
    for(int i=0;i<=n;i++) fa[i]=i;
    sort(E,E+m);
    int sum=0;
    for(int i=0;i<m;i++){
        int a=find_(E[i].u),b=find_(E[i].v);
        if(a!=b){
            fa[a]=b;
            addedge(E[i].u,E[i].v);
            addedge(E[i].v,E[i].u);
            sum+=E[i].w;
        }
    }
    return sum;
}
void dfs(int u,int f){
    dep[u]=dep[f]+1;
    vis[u]=1;
    for(int i=head[u];~i;i=nxt[i]) if(!vis[to[i]]){
        int v=to[i];
        p[v][0]=u;
        mx[v][0]=max(val[u],val[v]);
        mn[v][0]=min(val[u],val[v]);
        dp[v][0]=val[u]-val[v];
        dp2[v][0]=val[v]-val[u];
        for(int j=1;j<POW;j++){
            p[v][j]=p[p[v][j-1]][j-1];
            mx[v][j]=max(mx[v][j-1],mx[p[v][j-1]][j-1]);
            mn[v][j]=min(mn[v][j-1],mn[p[v][j-1]][j-1]);

            dp[v][j]=max(dp[v][j-1],dp[p[v][j-1]][j-1]);
            dp[v][j]=max(dp[v][j],mx[p[v][j-1]][j-1]-mn[v][j-1]);

            dp2[v][j]=max(dp2[v][j-1],dp2[p[v][j-1]][j-1]);
            dp2[v][j]=max(dp2[v][j],mx[v][j-1]-mn[p[v][j-1]][j-1]);
        }
        dfs(v,u);
    }
}
int LCA(int a,int b){
    //第一次看到这样的LCA,holy high
    //有点不明觉厉
    if(dep[a]>dep[b]) swap(a,b);
    if(dep[a]<dep[b]){
        //这一部分使得dep[a]==dep[b]
        int tmp=dep[b]-dep[a];
        for(int i=POW-1;i>=0;i--) if(tmp&(1<<i))
            b=p[b][i];
    }
    if(a!=b){
        //如果高度相等,而a!=b
        for(int i=POW-1;i>=0;i--) if(p[a][i]!=p[b][i])
            a=p[a][i],b=p[b][i];
        a=p[a][0],b=p[b][0];
    }
    return a;
}
int getmax(int x,int lca){
    int ans=0,tmp=dep[x]-dep[lca];
    for(int i=POW-1;i>=0;i--) if(tmp&(1<<i)){
        ans=max(ans,mx[x][i]);
        x=p[x][i];
    }
    return ans;
}
int getmin(int x,int lca){
    int ans=inf,tmp=dep[x]-dep[lca];
    for(int  i=POW-1;i>=0;i--) if(tmp&(1<<i)){
        ans=min(ans,mn[x][i]);
        x=p[x][i];
    }
    return ans;
}
int getleft(int x,int lca){
    int ans=0,minn=inf;
    int tmp=dep[x]-dep[lca];
    for(int i=POW-1;i>=0;i--) if(tmp&(1<<i)){
        ans=max(ans,dp[x][i]);
        ans=max(ans,mx[x][i]-minn);
        minn=min(minn,mn[x][i]);
        x=p[x][i];
    }
    return ans;
}
int getright(int x,int lca){
    int ans=0,maxx=0;
    int tmp=dep[x]-dep[lca];
    for(int i=POW-1;i>=0;i--) if(tmp&(1<<i)){
        ans=max(ans,dp2[x][i]);
        ans=max(ans,maxx-mn[x][i]);
        maxx=max(maxx,mx[x][i]);
        x=p[x][i];
    }
    return ans;
}
int main(){
    freopen("in.txt","r",stdin);
    while(~scanf("%d",&n)){
        for(int i=1;i<=n;i++)
            scanf("%d",&val[i]);
        ini(n);
        scanf("%d",&m);
        for(int i=0;i<m;i++)
            scanf("%d%d%d",&E[i].u,&E[i].v,&E[i].w);
        printf("%d\n",Kruskal());
        dfs(1,0);
        scanf("%d",&q);
        int x,y;
        while(q--){
            scanf("%d%d",&x,&y);
            int lca=LCA(x,y);
            int ans=getmax(y,lca)-getmin(x,lca);
            ans=max(ans,getleft(x,lca));
            ans=max(ans,getright(y,lca));
            printf("%d\n",ans);
        }
    }
    return 0;
}

时间: 2024-12-24 15:58:45

zoj 3649 lca与倍增dp的相关文章

Codeforces 1140G Double Tree 倍增 + dp

刚开始, 我以为两个点肯定是通过树上最短路径过去的, 无非是在两棵树之间来回切换, 这个可以用倍增 + dp 去维护它. 但是后来又发现, 它可以不通过树上最短路径过去, 我们考虑这样一种情况, 起点在奇树里面, 终点在偶树里面, 然后这两个点最短路径里面点到对应点的距离都很大, 这种情况下我们就需要从别的地方绕过去, 这样 就不是走树上最短路径了, 但是如果我们将对应点的距离更新成最短距离, 上面这个倍增 + dp的方法就可行了, 所以 我们可以用最短路去更新对应点之间的距离, 将它变成最小值

P5024 保卫王国[倍增+dp]

窝当然不会ddp啦,要写这题当然是考虑优化裸dp啦,但是这题非常麻烦,于是变成了黑题. 首先,这个是没有上司的舞会模型,求图的带权最大独立集. 不考虑国王的限制条件,有 \[ dp[x][0]+=dp[y][1]\dp[x][1]+=min(dp[y][1],dp[y][0]) \] 现在考虑限制条件,如果对每一个限制条件都做一次dp,复杂度达到\(O(n^2)\),无法承受. 显然,对于这些限制条件,每一次的变动不会影响其它大多数的状态. 对于一个限制条件,我们分开考虑,先考虑只对一个城市进行

ZOJ 3211 Dream City (J) DP

Dream City Time Limit: 1 Second      Memory Limit: 32768 KB JAVAMAN is visiting Dream City and he sees a yard of gold coin trees. There are n trees in the yard. Let's call them tree 1, tree 2 ...and tree n. At the first day, each tree i has ai coins

ZOJ 3551 Bloodsucker (概率DP)

ZOJ Problem Set - 3551 Bloodsucker Time Limit: 2 Seconds      Memory Limit: 65536 KB In 0th day, there are n-1 people and 1 bloodsucker. Every day, two and only two of them meet. Nothing will happen if they are of the same species, that is, a people

zoj 3791 An Easy Game dp

An Easy Game Time Limit: 2 Seconds      Memory Limit: 65536 KB One day, Edward and Flandre play a game. Flandre will show two 01-strings s1 and s2, the lengths of two strings are n. Then, Edward must move exact k steps. In each step, Edward should ch

[模板]LCA的倍增求法解析

题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每行包含两个正整数x.y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树). 接下来M行每行包含两个正整数a.b,表示询问a结点和b结点的最近公共祖先. 输出格式: 输出包含M行,每行包含一个正整数,依次为每一个询问的结果. 输入输出样例 输入样例#1: 5 5 4 3 1 2 4 5

ZOJ 3791 An easy game DP+组合数

给定两个01序列,每次操作可以任意改变其中的m个数字 0变 1  1 变 0,正好要变化k次,问有多少种变法 dp模型为dp[i][j],表示进行到第i次变化,A,B序列有j个不同的 变法总和. 循环k次,每次针对m,向那j个不同 分1-j个即可,不过要用到组合数,因为对每个数操作不同都不一样 最后结果就是 dp[k][0] #include <iostream> #include <cstdio> #include <cstring> #include <alg

HDU 4822 Tri-war(LCA树上倍增)(2013 Asia Regional Changchun)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4822 Problem Description Three countries, Red, Yellow, and Blue are in war. The map of battlefield is a tree, which means that there are N nodes and (N – 1) edges that connect all the nodes. Each country

ZOJ 3450 Doraemon&#39;s Railgun (DP&#183;分组背包)

题意  多啦A梦有一个超电磁炮  然后要打死n堆敌人  在同一条射线上的敌人只有先打死前面的一堆才能打后面的一堆  给你打死某堆敌人需要的时间和这堆敌人的人数   问你在T0时间内最多打死多少个敌人 分组背包问题  先要把同一条射线上的敌人放到一个分组里  后面的敌人的时间和人数都要加上前面所有的  因为只有前面的都打完了才能打后面的  然后每组最多只能选择一个   判断共线用向量处理   然后去背包就行了 注意给你的样例可能出现t=0的情况   在分组时需要处理一下    被这里卡了好久 #i