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; }
树链剖分求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; }
题意:给出一个长度为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; }
题意:给一棵树,每次询问给三个点,这三个点可以随意排列,分别为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; }
G HDU 5023
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; }