学习 LCA&&RMQ

参考:点击打开链接 点击打开链接      点击打开链接(一些总结) 点击打开链接(不错的模板)

题目:点击打开链接

花了4天时间做完了这个专题,LCA的问题用处还是很大,同时能体会RMQ的ST算法中dp的味道.基本方法就是ST,LCA转RMQ,LCA的Tarjan,LCA倍增(这个可存储边权)

这个专题后面四道题都非常好,推荐大家做做.

细节:

1. ST方法2^i 包含自己,因此其真实只包含到i+2^k-1的范围.

2. Tarjan一般都很快,但不适合修改类型的问题,关于权值长度之类的,ST就不能用了.比如E

3. LCA转RMQ记得有三个数组,长度为2*N-1 ,很容易写错,然后会奇怪的报TLE错误.rmq里面存放的是下标.........

4.关于树遍历判重,不是par就行,可以不用vis,要add(a,b) add(b,a)不然会冲突顶死.E题就犯这个错误了.

模板:

void ST(int x){
    int LOG=log(x+0.0)/log(2.0);
    for(int i=1;i<=x;i++) rmq[0][i]=i;
    for(int k=1;k<=LOG;k++)
        for(int i=1;i+(1<<k)-1<=x;i++){/// -1这里要注意
            int a=rmq[k-1][i],b=rmq[k-1][i+(1<<k-1)];
            if(dep[a]<dep[b]) rmq[k][i]=a;
            else rmq[k][i]=b;
    }
}
int RMQ(int a,int b){
    if(a>b) swap(a,b);
    int LOG=log(b-a+1.)/log(2.0);
    int c=rmq[LOG][a],d=rmq[LOG][b-(1<<LOG)+1];///+1这里要注意
    if(dep[c]<dep[d]) return c;
    else return d;
}
void Tarjan(int u){
    vis[u]=1;
    fa[u]=u;
    for(int i=head[u];~i;i=edge[i].nxt) if(!vis[edge[i].v]){
        int v=edge[i].v;
        whead[v]=whead[u]+edge[i].w;
        Tarjan(v);
        fa[v]=u;
    }
    for(int i=qhead[u];~i;i=query[i].nxt) if(vis[query[i].v]){
        int v=query[i].v;
        query[i].w=query[i^1].w=whead[u]+whead[v]-2*whead[Find(v)];///这里找祖先
    }
}

A - A Magic Lamp

从里面选几个数字使其最小,裸的RMQ  HDU3183

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<string.h>
using namespace std;
int dp[20][1024];
char num[1024],hash[1024];
int min( int i,int j )
{
   return num[i]<=num[j]?i:j;
}
void RMQ( int n )
{
    for( int i=0; i< n ;i++ )
       dp[0][i]=i;
    int t=( int  )( log( double( n ) )/log( 2.0 ) );
    for( int j=1; j<=t ; j++ )
       for( int i=0; i+( 1<<j )-1<n; i++ )
         dp[j][i]=min( dp[j-1][i],dp[j-1][i+( 1<<( j-1 ) )] );
}
int result( int left,int right )
{
   int k=( int )( log( double( right-left+1 ) )/log( 2.0 ) );
   return min( dp[k][left],dp[k][right-( 1<<k )+1] );
}
int main( )
{
    int n;
   while( scanf( "%s%d",num,&n )!=EOF ){
     int len=strlen( num );
     RMQ( len );
     int N=len-n;
     int i=0,j=0;
     while( N-- ){
       i=result( i,len-N-1 );
       hash[j++]=num[i++];
     }
     for( i=0;i<j;i++ ) if( hash[i]!='0' )
           break;
      if( i==j ) {printf( "0" );continue;}
         while( i<j ){
           printf( "%c" , hash[i] );
           i++;
         }
      puts( "" );
    }
   return 0;
}

B - Interviewe

HDU3486 直接枚举会超时,且并不符合二分的性质.自己慢慢优化吧.其实没什么意思.

C - Nearest Common Ancestors

POJ1330 LCA转RMQ问题,模板题

#include<ctime>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define inf 0x3f3f3f3f
#define eps 1e-8
#define long long ll;
#define lowbit(x) (x&-x)
const int MAXN = 2e4+10;
int N,M,T;
int nxt[MAXN],v[MAXN],head[MAXN],idx[2*MAXN],id,tot;
int dep[MAXN],pos[MAXN],in[MAXN];
int rmq[MAXN][20];
int min( int i,int j )
{
   return dep[i]<=dep[j]?i:j;
}
void init(){
    id=0;
    tot=0;
    memset(in,0,sizeof(in));
    memset(head,-1,sizeof(head));
}
void add(int a,int b){
    v[tot]=b;
    nxt[tot]=head[a];
    head[a]=tot++;
}
void dfs(int u,int d){
    idx[id]=u;
    dep[id]=d;
    pos[u]=id++;
    for(int i=head[u];~i;i=nxt[i]){
        dfs(v[i],d+1);
        idx[id]=u;
        dep[id++]=d;
    }
}
void RMQ(){
    for(int i=0;i<id;i++) rmq[i][0]=i;
    int K=log(id)/log(2.0);
    for(int k=1;k<=K;k++)
        for(int i=0;i+(1<<k)-1<id;i++)
            rmq[i][k]=min(rmq[i][k-1],rmq[i+(1<<(k-1))][k-1]);
}
int Q(int a,int b){
    if(a>b) swap(a,b);
    int K=log(b-a+1)/log(2.0);
    if(dep[min(rmq[a][K],rmq[b-(1<<K)+1][K])]==dep[rmq[a][K]])
        return rmq[a][K];
    return rmq[b-(1<<K)+1][K];
}

int main(){
    scanf("%d",&T);
    while(T--){
        init();
        scanf("%d",&N);
        for(int i=1;i<N;i++){
            int a,b;
            scanf("%d%d",&a,&b);
            add(a,b);
            in[b]++;
        }
        for(int i=1;i<=N;i++) if( !in[i]){
            dfs(i,1);
            break;
        }
        RMQ();
        int a,b;scanf("%d%d",&a,&b);
        printf("%d\n",idx[Q(pos[a],pos[b])]);
    }
    return 0;
}

D - Closest Common Ancestors

POJ1470 同模板题,不过输入输出要注意一下,网上有好方法.

while(getchar()!='(');
scanf("%d %d",&a,&b);
while(getchar()!=')');

E - Distance Queries

POJ1986 两点间距离.都求出到根的距离,然后dist[u]+dist[v]-2*dist[lca]就可以了.

query[i].w=query[i^1].w=whead[u]+whead[v]-2*whead[Find(v)];///这里找祖先

F - How far away ?

和E差不多.HDU2586

G - Design the city

ZOJ3195 题意:给一个无根树,有q个询问,每个询问3个点,问将这3个点连起来,距离最短是多少,

LCA的模板题,分别求LCA(X,Y),LCA(X,Z),LCA(Y,Z),和对应的距离,然后3个距离相加再除以2就是这个询问的结果

H - Connections between cities

模板题太多HDU2874

I - Network

HDU3078 这个题按照题意做就行,不会超时.因为有修改点权值的操作,所以不能用倍增的方法存储最优解.

#include<ctime>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define inf 0x3f3f3f3f
#define eps 1e-8
#define long long ll;
#define lowbit(x) (x&-x)
const int MAXN = 100000+10;
int N,M,K,T;
struct P{
    int to,nxt;
}edge[2*MAXN];
int head[MAXN],tot;
int num[MAXN],vis[MAXN];
int ans[MAXN];
int rmq[30][2*MAXN],dep[2*MAXN],pos[MAXN],seq[2*MAXN],id;///
int pre[MAXN];
bool cmp(int a, int b)
{
    return a > b;
}
void init(){
    tot=id=0;
    memset(vis,0,sizeof(vis));
    memset(pre,-1,sizeof(pre));
    memset(head,-1,sizeof(head));
}
void add(int a,int b){
    edge[tot].to=b;
    edge[tot].nxt=head[a];
    head[a]=tot++;
}
void RMQ(){
    int K=log(id+0.0)/log(2.0);
    for(int i=1;i<id;i++) rmq[0][i]=i;
    for(int i=1;i<=K;i++)
        for(int j=1;j+(1<<i)-1<id;j++)
            if(dep[ rmq[i-1][j] ]<dep[ rmq[i-1][j+(1<<(i-1))] ])
                rmq[i][j]=rmq[i-1][j];
            else rmq[i][j]=rmq[i-1][j+(1<<(i-1))];
}
int Q(int a,int b){
    if(a>b) swap(a,b);
    int K=log(b-a+1.0)/log(2.0);
    if(dep[ rmq[K][a] ]<dep[ rmq[K][b-(1<<K)+1] ])///
        return rmq[K][a];
    else return rmq[K][b-(1<<K)+1];
}
void dfs(int u,int d){
    vis[u]=1;
    seq[id]=u;
    dep[id]=d;
    pos[u]=id++;
    for(int i=head[u];~i;i=edge[i].nxt) if(!vis[edge[i].to]){
        pre[edge[i].to]=u;
        dfs(edge[i].to,d+1);
        seq[id]=u;
        dep[id++]=d;
    }
}
int main(){
    scanf("%d%d",&N,&M);
    init();
    for(int i=1;i<=N;i++)
        scanf("%d",num+i);
    for(int i=1;i<N;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);
        add(b,a);
    }
    dfs(1,1);RMQ();
    for(int i=0;i<M;i++){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        if(a==0) {num[b]=c;continue;}
        int d=seq[Q(pos[b],pos[c])];
        int cnt=0;
        for(int i=b;i!=d;i=pre[i])
            ans[cnt++]=num[i];
        for(int i=c;i!=d;i=pre[i])
            ans[cnt++]=num[i];
        ans[cnt++]=num[d];
        sort(ans,ans+cnt,cmp);
        if(cnt<a) puts("invalid request!");
        else printf("%d\n",ans[a-1]);
    }
    return 0;
}

J - Housewife Wind

poj2763  好题!用树状数组优化加速.这里对于欧拉数组,有个特点改变一个点或者该点父边.则受影响的孩子都在欧拉数组中间.first为第一次访问点,second为结束访问点.若有改变

就add(first[u],x),add(second[u]+1,-x) 求和就是sum(x)+sum(y)-sum(lca);

K - Network

poj3694  和AC程序对拍了很多数据都一样但是RE,懒得找问题了.

用Tarjan缩点

#include<ctime>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define inf 0x3f3f3f3f
#define eps 1e-8
#define long long ll;
#define lowbit(x) (x&-x)
const int MAXN = 400000+10;
int N,M,S,K,T;
struct P{
    int st,to,w,nxt;
}edge[2*MAXN];
int vis[MAXN],head[MAXN],tot,idx;
int dep[MAXN],fa[MAXN],parent[MAXN];
int dfn[MAXN],low[MAXN],isbridge[MAXN],bcnt;
int mystack[MAXN],top,instack[MAXN];
int Find(int x){return x==fa[x]?x:fa[x]=Find(fa[x]);}
void init(){
    tot=bcnt=idx=top=0;
    memset(vis,0,sizeof(vis));
    memset(head,-1,sizeof(head));
    memset(fa,-1,sizeof(fa));
    memset(isbridge,0,sizeof(isbridge));
}
void add(int a,int b){
    edge[tot].st=a;
    edge[tot].to=b;
    edge[tot].nxt=head[a];
    head[a]=tot++;
}

void Tarjan(int u,int d){
    dfn[u]=low[u]=idx++;
    mystack[top++]=u;
    vis[u]=1;
    dep[u]=d;
    for(int i=head[u];~i;i=edge[i].nxt) {
        int v=edge[i].to;
        if(!vis[v]){
            parent[v]=u;///回溯上去
            Tarjan(v,d+1);
            low[u]=min(low[u],low[v]);
            if(dfn[u]<low[v]) bcnt++,isbridge[v]=1;///点的父边代表其
        }
        else if(vis[v] == 1) low[u]=min(low[u],dfn[v]);/// double edge ignore it 若本身有反向边就不存在这个了
    }
    if(dfn[u]==low[u]){
        int k;
        do{
            k=mystack[--top];
            fa[k]=u;
        }while(top && k!=u);
    }
    vis[u]=2;
}
void LCA(int a,int b){
    if(dfn[a]<dfn[b]) swap(a,b);
    int c=Find(a),d=Find(b);
    if(c==d && c!=-1) return;
    while(dep[a]>dep[b]){
        if(isbridge[a]) bcnt--;
        isbridge[a]=0;
        a=parent[a];
    }
    while(a!=b){
        if(isbridge[a]) bcnt--;
        if(isbridge[b]) bcnt--;
        isbridge[a]=isbridge[b]=0;
        a=parent[a];b=parent[b];
    }
    fa[c]=d;
}
int main(){
    int q,cas=0;
    while(~scanf("%d%d",&N,&M)){
        if(N==0 && M==0) break;
        init();
        for(int i=0;i<M;i++){
            int a,b;
            scanf("%d%d",&a,&b);
            add(a,b);
        }
        Tarjan(1,1);
        printf("Case %d:\n",++cas);
        scanf("%d",&q);
        for(int i=0;i<q;i++){
            int u,v;
            scanf("%d%d",&u,&v);
            LCA(u,v);
            printf("%d\n",bcnt);
        }
        puts("");
    }
    return 0;
}

L - Network

poj3417 好题,减掉一条原来就有的边,减掉一条增加的边,问有几种方法可以让图变成两部分.

想一下就知道若是树的话每条都是关键的,那么新边随便减 M

若被环覆盖一次,只要减去构成环的那条边,1种方法,

若>1则没用``````

思路是这个,但是实现很有技巧,dp[u]表示被环覆盖几次. 对一条新边(u,v),则有dp[u]++,dp[v]++ ,dp[lca]-=2

最后在dfs一次累加上去就OK.dp[u]+=dp[v]

#include<ctime>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define inf 0x3f3f3f3f
#define eps 1e-8
#define long long ll;
#define lowbit(x) (x&-x)
const int MAXN = 100000+10;
int N,M,S,K,T;
struct P{
    int st,to,nxt;
}edge[2*MAXN];
int head[MAXN],dep[MAXN<<1],idx[MAXN<<1],first[MAXN],id,tot;
int vis[MAXN],dp[MAXN];
int rmq[30][MAXN<<1];

void init(){
    tot=id=0;
    memset(dp,0,sizeof(dp));
    memset(vis,0,sizeof(vis));
    memset(head,-1,sizeof(head));
}
void ST(int x){
    int LOG=log(x+0.0)/log(2.0);
    for(int i=1;i<=x;i++) rmq[0][i]=i;
    for(int k=1;k<=LOG;k++)
        for(int i=1;i+(1<<k)-1<=x;i++){/// -1
            int a=rmq[k-1][i],b=rmq[k-1][i+(1<<k-1)];
            if(dep[a]<dep[b]) rmq[k][i]=a;
            else rmq[k][i]=b;
    }
}
int RMQ(int a,int b){
    if(a>b) swap(a,b);
    int LOG=log(b-a+1.)/log(2.0);
    int c=rmq[LOG][a],d=rmq[LOG][b-(1<<LOG)+1];///+1
    if(dep[c]<dep[d]) return c;
    else return d;
}
void add(int a,int b){
    edge[tot].st=a;
    edge[tot].to=b;
    edge[tot].nxt=head[a];
    head[a]=tot++;
}
void dfs(int u,int d,int par){
    dep[++id]=d;
    idx[id]=u;first[u]=id;
    for(int i=head[u];~i;i=edge[i].nxt) if(edge[i].to !=par){
        dfs(edge[i].to,d+1,u);
        idx[++id]=u;
        dep[id]=d;
    }
}
int getans(int u,int par){
    int res=0;
    for(int i=head[u];~i;i=edge[i].nxt) if(edge[i].to!=par){
        res+=getans(edge[i].to,u);
        dp[u]+=dp[edge[i].to];
    }
    if(u==1 || dp[u]>=2) return res;
    if(dp[u]==0) return res+M;
    if(dp[u]==1) return res+1;
}

int main(){
    int q,cas=0;
    while(~scanf("%d%d",&N,&M)){
        init();
        for(int i=1;i<N;i++){
            int a,b;
            scanf("%d%d",&a,&b);
            add(a,b);
            add(b,a);
        }
        dfs(1,1,-1);ST(id);
        for(int i=0;i<M;i++){
            int u,v;
            scanf("%d%d",&u,&v);
            int lca=idx[RMQ(first[u],first[v])];
            dp[u]++;dp[v]++;dp[lca]-=2;
        }

        printf("%d\n",getans(1,-1));
    }
    return 0;
}

M - The merchant

poj 3728 题意大致是从两个点间倒手货物,使得获利最大,求价值.

这个题暗含了方向的意思,买卖次序不能颠倒,我刚开始一直用倍增方法找最小最大,那么久分两种u--->lca--->v 那么三种情况

1. 在u--->lca过程中买卖完成    2. lca--->v 买卖完成   3.u--->lca 买   lca--->v 卖

那么使用Tarjan方法每次维护四个数组

mx u到lca最大值  mn u到lca最小值 up 从u到lca最大获利,  down 从lca到u最大获利.

那么一条路径(u,v) 的最大获利=max(up[u],down[u],mx[v],mn[u]); 最后面这个因为是低买高卖

如何维护就类似于关系并查集了.并且要逐层回答,因为这几个数组网上情况会变,因此要将同lca的询问一起回答.

关系并查集就是我 和父亲的关系 父亲和祖先的关系 推导出我和祖先的关系,对应此题就是求所维护的数组中数最优.

#include<ctime>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define inf 0x3f3f3f3f
#define eps 1e-8
#define long long ll;
#define lowbit(x) (x&-x)
const int MAXN = 100000+10;
int N,M,S,K,T;

struct P{
    int st,to,nxt,id;
}edge[MAXN<<3];
int head[MAXN],head2[MAXN],head3[MAXN],id,tot;
int fa[MAXN],cost[MAXN],vis[MAXN];
int mx[MAXN],mn[MAXN],up[MAXN],down[MAXN];
int ans[MAXN];
int Find(int x){
    if(x==fa[x]) return x;
    int t=fa[x];
    fa[x]=Find(fa[x]);
    up[x]=max(up[t],max(up[x],mx[t]-mn[x]));///
    down[x]=max(down[t],max(down[x],mx[x]-mn[t]));
    mx[x]=max(mx[x],mx[t]);
    mn[x]=min(mn[x],mn[t]);
    return fa[x];
}
void init(){
    tot=id=0;
    memset(vis,0,sizeof(vis));
    memset(head,-1,sizeof(head));
    memset(head2,-1,sizeof(head2));
    memset(head3,-1,sizeof(head3));
}

void add(int a,int b,int c){
    edge[tot].id=c;
    edge[tot].st=a;
    edge[tot].to=b;
    edge[tot].nxt=head[a];
    head[a]=tot++;
}
void add_ask(int a,int b,int c){
    edge[tot].id=c;
    edge[tot].st=a;
    edge[tot].to=b;
    edge[tot].nxt=head2[a];
    head2[a]=tot++;
}
void Tarjan(int u,int par){
    fa[u]=u;vis[u]=1;
    for(int i=head[u];~i;i=edge[i].nxt) if(edge[i].to !=par){
        Tarjan(edge[i].to,u);
        fa[edge[i].to]=u;
    }
    for(int i=head2[u];~i;i=edge[i].nxt) if(vis[edge[i].to]){
        int t=Find(edge[i].to);
        edge[tot].id=i;
        edge[tot].nxt=head3[t];
        head3[t]=tot++;
    }///回溯只有逐层LCA才有效,利用根的唯一性.之前就是找到就回溯,WA
    for(int i=head3[u];~i;i=edge[i].nxt){
        int k=edge[i].id;
        int a=edge[k].st,b=edge[k].to,c=edge[k].id;
        if(ans[abs(c)]) continue;
        Find(a);
        if(c<0) swap(a,b),c=-c;
        ans[c]=max( up[a], max(down[b],mx[b]-mn[a]) );
    }
}
///
int main(){
    while(~scanf("%d",&N)){
        init();
        for(int i=1;i<=N;i++) scanf("%d",cost+i),mn[i]=mx[i]=cost[i],up[i]=down[i]=0;
        for(int i=1;i<N;i++){
            int a,b;
            scanf("%d%d",&a,&b);
            add(a,b,i);
            add(b,a,-i);
        }
        int q;
        scanf("%d",&q);
       for(int i=1;i<=q;i++){
            int a,b;
            scanf("%d%d",&a,&b);
            add_ask(a,b,i);
            add_ask(b,a,-i);
        }
        Tarjan(1,-1);
        for(int i=1;i<=q;i++)
            printf("%d\n",ans[i]);
    }
    return 0;
}

N - Checkers

HDU 3830  任何一个状态,通过题目所给的移动,都能对应且唯一对应一个b*2=a+c(a<b<c)的状态,这是突破点

其三个点合起来看做一个状态,至多三种转移状态.因此就很像二叉树,并且根的状态唯一,左右两边相等.所以以这个为根

如果不是根的状态,可以让左右两个往里跳,依据是让和中间那个坐标距离缩小

这样就可以看作为是向根移动 图并不用显示构造出来,用辗转相除的思想就可以解决,

然后二分+LCA.这里的二分注意,从点出发往根走,满足题意了才是减少步数,且不像一般的二分high=mid-1.因为这样很可能就过头了

#include<ctime>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define inf 0x3f3f3f3f
#define eps 1e-8
typedef long long ll;
#define lowbit(x) (x&-x)
const int MAXN = 100000+10;
int N,M,S,K,T;

typedef struct {
    ll a,b,c,dep;
}state;
state st,ed,srt,ert;
int head[MAXN],head2[MAXN],head3[MAXN],id,tot;
int fa[MAXN],cost[MAXN],vis[MAXN];
int ans[MAXN];

void mysort(state &x){
    if(x.a>x.b) swap(x.a,x.b);
    if(x.a>x.c) swap(x.a,x.c);
    if(x.b>x.c) swap(x.b,x.c);
}
bool cmp(state a,state b){
    if(a.a==b.a && a.b==b.b && a.c==b.c) return true;
    return false;
}
state findroot(state& x){
    ll dep=0;
    ll a=x.a,b=x.b,c=x.c,t;
    while(b-a!=c-b){
        ll len=b-a,_len=c-b;
        if(len>_len){
            t=(len-1)/_len;
            b-=t*_len;
            c-=t*_len;
        }
        else{
            t=(_len-1)/len;
            b+=t*len;
            a+=t*len;
        }
        dep+=t;
    }
    x.dep=dep;
    state res={a,b,c,0};
    return res;
}
state update(state x,ll delta){
    ll a=x.a,b=x.b,c=x.c,t;
    while(delta>0){
        ll len=b-a,_len=c-b;
        if(len>_len){
            t=(len-1)/_len;
            if(t>delta) t=delta;
            b-=t*_len;
            c-=t*_len;
        }
        else{
            t=(_len-1)/len;
            if(t>delta) t=delta;
            b+=t*len;
            a+=t*len;
        }
        delta-=t;
    }
    state res={a,b,c};
    return res;
}
int main(){

    while(~scanf("%I64d%I64d%I64d",&st.a,&st.b,&st.c)){
        scanf("%I64d%I64d%I64d",&ed.a,&ed.b,&ed.c);
        mysort(st);mysort(ed);
        srt=findroot(st);
        ert=findroot(ed);
        if(!cmp(srt,ert)){
            puts("NO");continue;
        }
        if(st.dep<ed.dep) swap(st,ed);
        ll ans=st.dep-ed.dep;
        st=update(st,ans);
        ll l=0,h=ed.dep;
        state t1,t2;
        while(l<h){
            ll mid=(l+h)>>1;
            t1=st,t2=ed;
            t1=update(st,mid);
            t2=update(ed,mid);
            if(cmp(t1,t2)) h=mid;///二分法起初多写了个-1,WA
            else l=mid+1;
        }
        printf("YES\n%I64d\n",ans+2*l);
    }
    return 0;
}

时间: 2024-08-06 07:57:44

学习 LCA&&RMQ的相关文章

UESTC 912 树上的距离 --LCA+RMQ+树状数组

1.易知,树上两点的距离dis[u][v] = D[u]+D[v]-2*D[lca(u,v)] (D为节点到根节点的距离) 2.某条边<u,v>权值一旦改变,将会影响所有以v为根的子树上的节点到根节点的距离,很明显,DFS一遍后以v为根的子树在DFS序列中是连续的一段,及转化为区间更新问题,可以用树状数组. 做法:先把求LCA解决,LCA可以转化为RMQ问题,可参见:LCA转RMQ, 即转化为LCA(T,u,v) = RMQ(B,pos[u],pos[v]),其中B为深度序列.预先DFS可以处

POJ 1470 Closest Common Ancestors(LCA&amp;RMQ)

题意比较费劲:输入看起来很麻烦.处理括号冒号的时候是用%1s就可以.还有就是注意它有根节点...Q次查询,我是用在线st做的. /************************************************************************* > File Name: 3.cpp > Author: Howe_Young > Mail: [email protected] > Created Time: 2015年10月08日 星期四 19时03分

LCA&amp;&amp;RMQ问题

参考:点击打开链接 点击打开链接      点击打开链接(一些总结) 点击打开链接(不错的模板) 题目:点击打开链接 花了4天时间做完了这个专题,LCA的问题用处还是很大,同时能体会RMQ的ST算法中dp的味道.基本方法就是ST,LCA转RMQ,LCA的Tarjan,LCA倍增(这个可存储边权) 这个专题后面四道题都非常好,推荐大家做做. 细节: 1. ST方法2^i 包含自己,因此其真实只包含到i+2^k-1的范围. 2. Tarjan一般都很快,但不适合修改类型的问题,关于权值长度之类的,S

poj2763(lca / RMQ + 线段树)

题目链接: http://poj.org/problem?id=2763 题意: 第一行输入 n, q, s 分别为树的顶点个数, 询问/修改个数, 初始位置. 接下来 n - 1 行形如 x, y, w 的输入为点 x, y 之间连边且边权为 w. 接下来 q 行输入, 若输入形式为 1 x y 则为将点 x 的权值修改为 y , 若输入形式为 0 x 则询问 s 到 x 的最短距离为多少. 上一组的 x 为下一组的 s. 思路: 若去掉修改边权部分, 则为一个 lca 模板题. 对于修改边权

hdu3078(lca / RMQ在线)

题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=3078 题意: 给出一棵 n 个点的带点权值的树, 接下来有 q 组形如 k, x, y 的输入, 若 k == 0 则将 x 点的权值替换成 y, 否则输出 x 到 y 之间顶点地 k 大的权值. 思路: 用一个数组 val 记录一下每个顶点的权值, 对于k == 0, 直接令 val[x] = y 即可 . 对于询问, 可以先求出 lca, 再记录一下路径上的顶点的权值, sort 一下, 输出

zoj3195(lca / RMQ离线)

题目链接: http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3195 题意: 给出一棵 n 个节点的带边权的树, 有 q 组形如 x, y, z 的询问, 输出 x, y, z之间的最短路径. 思路: 在纸上画下不难发现 x, y, z之间的最短路径就是 x, y, z 两两之间的最短路径和的一半. 我们可以通过 lca 模板求出 x, y, z 两两之间的最短路径, 然后再算下 x, y, z三点之间的最短路径即可. 这

HDU - 6393 Traffic Network in Numazu (LCA+RMQ+树状数组)

这道题相当于将这两题结合: http://poj.org/problem?id=2763 http://codeforces.com/gym/101808/problem/K 题意:有N各点N条边的带权无向图(相当于一棵树多了一条边),两种操作:修改一条边的权值:求两点间的最短路径. 分析:将任意一条边取出来,其余n-1条边可以结合LCA解最短路.询问时,比较通过取出的边和仅通过树上的边的路径的大小,最小值就是两点的最短路径. 树状数组差分维护点到根节点的距离,根据dfs序来记录需要维护的范围.

暑假集训 || LCA &amp;&amp; RMQ

LCA定义为对于一颗树 树上两个点的最近公共祖先 一.Tarjan求LCA(离线方法 https://blog.csdn.net/lw277232240/article/details/77017517 二.倍增法求LCA void dfs(int u, int f) { for(int i = 1; i <= 18; i++) if(deep[u] >= (1<<i)) fa[u][i] = fa[fa[u][i-1]][i-1]; for(int i = head[u];i;i

POJ3728 LCA RMQ DP

题意简述:给定一个N个节点的树,1<=N<=50000 每个节点都有一个权值,代表商品在这个节点的价格.商人从某个节点a移动到节点b,且只能购买并出售一次商品,问最多可以产生多大的利润. 算法分析:显然任意两个城市之间的路径是唯一的,商人有方向地从起点移动到终点.询问这条路径上任意两点权值之差最大为多少,且要保证权值较大的节点在路径上位于权值较小的节点之后. 暴力的方法是显而易见的,只要找到两个点的深度最深的公共祖先,就等于找到了这条路径,之后沿着路径走一遍即可找到最大的利润,然而无法满足50