HIT暑期集训 lca与rmq

 rmq,求区间最大最小值模板,以POJ - 3264为例

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define maxn 50005
using namespace std;
int a[maxn],dp[maxn][30][2];
void ST(int n) 
{
    int i,j;
    for (i=1;i<=n;i++) dp[i][0][0]=dp[i][0][1]=a[i];
    for (j=1;(1<<j)<=n;j++) 
    {
        for (i=1;i+(1<<j)-1<=n;i++) 
        {
            dp[i][j][0]=max(dp[i][j-1][0],dp[i+(1<<(j-1))][j-1][0]);
            dp[i][j][1]=min(dp[i][j-1][1],dp[i+(1<<(j-1))][j-1][1]);
        }
    }
}
int getk(int l,int r)
{
    int k=0;
    while ((1<<(k+1))<=r-l+1) k++;
    return k;
}
int rmq_max(int l,int r,int k) 
{
    return max(dp[l][k][0],dp[r-(1<<k)+1][k][0]);
}
int rmq_min(int l,int r,int k) 
{
    return min(dp[l][k][1],dp[r-(1<<k)+1][k][1]);
}
int main()
{
    int i,x,y,k,n,q,ans;
    scanf("%d%d",&n,&q);
    for (i=1;i<=n;i++) scanf("%d",&a[i]);
    ST(n);
    while (q--) 
    {
        scanf("%d%d",&x,&y);
        k=getk(x,y);
        ans=rmq_max(x,y,k)-rmq_min(x,y,k);
        printf("%d\n",ans);
    }
    return 0;
}
rmq模板

树链剖分求lca模板,以POJ - 1330为例

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define maxn 10005
using namespace std;
struct edge
{
    int to,nxt;
}e[maxn];
int num,last[maxn],deg[maxn];
int fa[maxn],dep[maxn],siz[maxn],son[maxn],top[maxn];
void add(int x,int y)
{
    e[++num].to=y;
    e[num].nxt=last[x];
    last[x]=num;
}
void dfs1(int u,int father,int depth)  
{
    int i,v;
    fa[u]=father;
    dep[u]=depth;
    siz[u]=1;
    for (i=last[u];i;i=e[i].nxt)
    {
        v=e[i].to;
        if (v==father) continue;
        dfs1(v,u,depth+1);    
        siz[u]+=siz[v];    
        if (siz[v]>siz[son[u]]) son[u]=v;   
    }
}
void dfs2(int u,int tp)   
{
    top[u]=tp; 
    if (!son[u]) return;
    dfs2(son[u],tp);
    int i,v;
    for (i=last[u];i;i=e[i].nxt)
    {
        v=e[i].to;
        if (v!=son[u] && v!=fa[u]) dfs2(v,v);  
    }
}
int query(int x,int y)
{
    int tx=top[x],ty=top[y];
    while (tx!=ty)    
    {
        if (dep[tx]>=dep[ty]) x=fa[tx],tx=top[x];
        else y=fa[ty],ty=top[y];
    }
    if (dep[x]>dep[y]) return y;
    return x;
}
int main()
{
    int T,i,x,y,n,rt;
    scanf("%d",&T);
    while (T--)
    {
        num=0;
        memset(last,0,sizeof(last));
        memset(son,0,sizeof(son));
        memset(deg,0,sizeof(deg));
        scanf("%d",&n);
        for (i=1;i<n;i++)
        {
            scanf("%d%d",&x,&y);
            add(x,y);deg[y]++;
        }
        for (i=1;i<=n;i++)
            if (deg[i]==0) 
            {
                rt=i;break;
            }
        dfs1(rt,0,0);
        dfs2(rt,0);
        scanf("%d%d",&x,&y);
        printf("%d\n",query(x,y));
    }
    return 0;
}
树链剖分求lca

D    HYSBZ 1047

E    CodeForces 803G

题意:给出一个长度为n的区间,将它复制k倍形成一个长度为n*k的区间。接下来有q个操作,操作1将区间(l,r)中的所有数改为x,操作2询问区间(l,r)中的最小值。

其中n<=1e5,k<=1e4,q<=1e5

思路:动态开点线段树+rmq。

n*k<=1e9,显然不能直接使用rmq或者线段树求解。

发现q<=1e5,若动态开点线段树(就是每次操作到一个新的区间再开新的点),q次操作最多增加2*q*log(n*k)(<=6e6)个节点。

于是用rmq维护最开始的n长区间内的最小值,当开点时根据rmq为线段树节点赋值。

#include<cstdio> 
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn=100010;
const int maxnode=6000010;
const int inf=1e9+7; 
int tot,n,a[maxn],dp[maxn][30],prelog2[maxn];
struct node
{
    int val,tag;
    int ch[2];
}tr[maxnode];
void prework(int n)//预处理,求对数 
{
    prelog2[0]=-1;
    for (int i=1;i<=n;i++) 
        if (((i-1)&i)==0) prelog2[i]=prelog2[i-1]+1;//此时i为2^k 
        else prelog2[i]=prelog2[i-1];
}
void ST(int n)//初始数组的区间最小值 
{
    int i,j;
    for (i=1;i<=n;i++) dp[i][0]=a[i];
    for (j=1;(1<<j)<=n;j++) 
        for (i=1;i+(1<<j)-1<=n;i++) 
            dp[i][j]=min(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
}
int st_query(int l,int r) 
{
    int k=prelog2[r-l+1];
    return min(dp[l][k],dp[r-(1<<k)+1][k]);
}
void pushup(int k)
{
    tr[k].val=min(tr[tr[k].ch[0]].val,tr[tr[k].ch[1]].val);
}
void pushdown(int k)
{
    tr[tr[k].ch[0]].val=tr[k].tag;
    tr[tr[k].ch[0]].tag=tr[k].tag;
    tr[tr[k].ch[1]].val=tr[k].tag;
    tr[tr[k].ch[1]].tag=tr[k].tag;
    tr[k].tag=0;
}
void newnode(int k,int t,int l,int r) 
{
    tr[k].ch[t]=++tot;//新节点编号 
    //新节点所代表的区间没有被修改过 
    if (r-l+1>=n) tr[tot].val=st_query(1,n);
    else 
    {
        int ll=l%n,rr=r%n;
        if (!ll) ll=n;
        if (!rr) rr=n;
        if (rr<ll) tr[tot].val=min(st_query(1,rr),st_query(ll,n));
        else tr[tot].val=st_query(ll,rr);
    }
}
void update(int L,int R,int l,int r,int k,int x)
{
    if (L<=l && r<=R)
    {
        tr[k].val=x;
        tr[k].tag=x;
        return;
    }
    int mid=(l+r)>>1;
    if (!tr[k].ch[0]) newnode(k,0,l,mid);
    if (!tr[k].ch[1]) newnode(k,1,mid+1,r);
    if (tr[k].tag) pushdown(k);
    if (L<=mid) update(L,R,l,mid,tr[k].ch[0],x);
    if (R>mid) update(L,R,mid+1,r,tr[k].ch[1],x);
    pushup(k);
}
int query(int ql,int qr,int l,int r,int k)
{
    if (ql<=l && r<=qr) return tr[k].val;
    int mid=(l+r)>>1;
    if (!tr[k].ch[0]) newnode(k,0,l,mid);
    if (!tr[k].ch[1]) newnode(k,1,mid+1,r);
    int re=inf;
    if (tr[k].tag) pushdown(k);
    if (ql<=mid) re=min(re,query(ql,qr,l,mid,tr[k].ch[0]));
    if (qr>mid) re=min(re,query(ql,qr,mid+1,r,tr[k].ch[1]));
    pushup(k);
    return re;
}
int main()
{
    int op,l,r,x;
    int i,k,q;
    scanf("%d%d",&n,&k);
    for (i=1;i<=n;i++) scanf("%d",&a[i]);
    prework(n);
    ST(n);
    tr[1].val=st_query(1,n);
    tot=1;
    scanf("%d",&q);
    while (q--)
    {
        scanf("%d",&op);
        if (op==1)
        {
            scanf("%d%d%d",&l,&r,&x);
            update(l,r,1,n*k,1,x);
        }
        else 
        {
            scanf("%d%d",&l,&r);
            printf("%d\n",query(l,r,1,n*k,1));
        }
    }
    return 0;
}
View Code

F    CodeForces 832D

题意:给一棵树,每次询问给三个点,这三个点可以随意排列,分别为s,f,t,问s到f的路径上与t到f的路径上有几个两路径共有的节点。

可以发现答案为(dis[s->f]+dis[t->f]-dis[s->t])/2+1(由于是求共有节点所以要+1),而dis[x->y]=dep[x]+dep[y]-2*dep[lca(x,y)],

用树链剖分就可求出dep与lca,从而求出dis。

对于每次询问,枚举三种f的情况,其中的最大值就是答案。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define maxn 100005
using namespace std;
struct edge
{
    int to,nxt;
}e[maxn<<1];
int num,last[maxn];
int fa[maxn],dep[maxn],siz[maxn],son[maxn],top[maxn];
void add(int x,int y)
{
    e[++num].to=y;
    e[num].nxt=last[x];
    last[x]=num;
}
void dfs1(int u,int father,int depth)  
{
    int i,v;
    fa[u]=father;
    dep[u]=depth;
    siz[u]=1;
    for (i=last[u];i;i=e[i].nxt)
    {
        v=e[i].to;
        if (v==father) continue;
        dfs1(v,u,depth+1);    
        siz[u]+=siz[v];    
        if (siz[v]>siz[son[u]]) son[u]=v;   
    }
}
void dfs2(int u,int tp)   
{
    top[u]=tp;
    if (!son[u]) return;
    dfs2(son[u],tp);
    int i,v;
    for (i=last[u];i;i=e[i].nxt)
    {
        v=e[i].to;
        if (v!=son[u] && v!=fa[u]) dfs2(v,v);  
    }
}
int lca(int x,int y)
{
    int tx=top[x],ty=top[y];
    while (tx!=ty)    
    {
        if (dep[tx]>=dep[ty]) x=fa[tx],tx=top[x];
        else y=fa[ty],ty=top[y];
    }
    if (dep[x]>dep[y]) return y;
    return x;
}
int getdis(int x,int y)
{
    int xy=lca(x,y);
    return dep[x]+dep[y]-2*dep[xy];
} 
int getans(int s,int f,int t)
{
    return (getdis(s,f)+getdis(f,t)-getdis(s,t))/2;
} 
int main()
{
    int T,i,x,y,z,n,q,ans;
    while (scanf("%d%d",&n,&q)!=EOF)
    {
        num=0;
        memset(last,0,sizeof(last));
        memset(son,0,sizeof(son));
        for (i=2;i<=n;i++)
        {
            scanf("%d",&x);
            add(x,i);add(i,x);
        }
        dfs1(1,0,0);
        dfs2(1,0);
        while (q--)
        {
            scanf("%d%d%d",&x,&y,&z);
            ans=getans(x,y,z);
            ans=max(ans,getans(y,x,z));
            ans=max(ans,getans(x,z,y));
            printf("%d\n",ans+1);
        }
    }
    return 0;
}
View Code

G    HDU 5023

H    HYSBZ 4810

J    POJ 2019

题意:给定一个n*n矩阵与整数b,每次询问输入x,y,求以(x,y)为左上顶点的b*b矩阵中的最大值与最小值的差。

二维rmq。对于每行做rmq,即上文rmq模板中的dp[i][j][0/1]加上一维改为dp[k][i][j][0/1],

其中k代表行数,i为区间左端点,i+2j为区间右端点,第四维0代表区间最大值,1代表区间最小值。

然后对于每次询问for一遍行数统计答案就完事了。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define maxn 255
using namespace std;
int n,b,a[maxn][maxn];
int dp[maxn][maxn][30][2];
//第一维代表rmq维护的行数。第四维0代表区间最大值,1代表区间最小值。 
void ST() 
{
    int i,j,k;
    for (k=1;k<=n;k++)
    for (i=1;i<=n;i++) dp[k][i][0][0]=dp[k][i][0][1]=a[k][i];
    for (k=1;k<=n;k++)
    for (j=1;(1<<j)<=n;j++) 
    {
        for (i=1;i+(1<<j)-1<=n;i++) 
        {
            dp[k][i][j][0]=max(dp[k][i][j-1][0],dp[k][i+(1<<(j-1))][j-1][0]);
            dp[k][i][j][1]=min(dp[k][i][j-1][1],dp[k][i+(1<<(j-1))][j-1][1]);
        }
    }
}
int main()
{
    int i,j,x,y,k,b,q,ans[2];
    while (scanf("%d%d%d",&n,&b,&q)!=EOF)
    {
        for (i=1;i<=n;i++) 
        for (j=1;j<=n;j++) scanf("%d",&a[i][j]);
        ST();
        k=0;
        while ((1<<(k+1))<=b) k++;
        while (q--) 
        {
            ans[0]=-1;ans[1]=251;
            scanf("%d%d",&x,&y);
            for (i=x;i<x+b;i++)
            {
                ans[0]=max(ans[0],dp[i][y][k][0]);
                ans[0]=max(ans[0],dp[i][y+b-(1<<k)][k][0]);
                ans[1]=min(ans[1],dp[i][y][k][1]);
                ans[1]=min(ans[1],dp[i][y+b-(1<<k)][k][1]);
            }
            printf("%d\n",ans[0]-ans[1]);
        }
    }
    return 0;
}
View Code

 

posted @ 2020-08-20 22:53  lsy_kk  阅读(115)  评论(0编辑  收藏  举报