旅行(树剖+主席树)

旅行(树剖+主席树)

给定一个n(<=1e5)的图,m(<=1e5)个操作,每次标记第ci个点,或者询问a到b的路径中,在第y个操作之后没有被标记的第k个点。

树剖+主席树维护重链。就说三点(说多了都是泪):1. 线段树上二分要注意min max。2. windows的缺省栈值只有4m,所以递归1e5层铁定爆栈了(我的程序3e4层就爆了)。 3. 暴力算法,通常,隐藏的很深。

……

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

const int maxn=1e5+5, INF=0x3f3f;
int n, m, root;
struct Edge{
    int to, nxt;
}e[maxn*2];
int cnte, fir[maxn];
void addedge(int x, int y){
    Edge &ed=e[++cnte];
    ed.to=y; ed.nxt=fir[x]; fir[x]=cnte; }

int dep[maxn], siz[maxn], son[maxn], fa[maxn];
void dfs1(int now, int fat, int depth){
    dep[now]=depth; siz[now]=1;
    int maxm=0; fa[now]=fat;
    for (int i=fir[now]; i; i=e[i].nxt){
        dfs1(e[i].to, now, depth+1);
        siz[now]+=siz[e[i].to];
        if (siz[e[i].to]>maxm){
            maxm=siz[e[i].to]; son[now]=e[i].to; }
    }
}

int top[maxn], id[maxn], ori[maxn], cnt;  //id[i]:结点i在主席树中位置的编号
void dfs2(int now){
    id[now]=++cnt; ori[cnt]=now;
    for (int i=fir[now]; i; i=e[i].nxt)
        if (e[i].to==son[now]){
            top[e[i].to]=top[now];
            dfs2(e[i].to); }
    for (int i=fir[now]; i; i=e[i].nxt)
        if (e[i].to!=son[now]){
            top[e[i].to]=e[i].to;
            dfs2(e[i].to);
        }
}

int rt[maxn], cntnode;
struct Node{
    int x, l, r;  //x表示有多少点*没有*被标记过
}node[maxn*20];
//now:当前主席树的结点编号 ori:前一个主席树的同位点编号
void ins(int &now, int ori, int l, int r, int pos){
    now=++cntnode; node[now]=node[ori];
    if (l==r){ node[now].x=1; return;  }
    int mid=(l+r)>>1;
    if (pos<=mid) ins(node[now].l, node[ori].l, l, mid, pos);
    else ins(node[now].r, node[ori].r, mid+1, r, pos);
    ++node[now].x;  //保证区间内被标的点数+1
}
int query(int now, int l, int r, int L, int R){
    if (!now) return 0;
    if (l>r) return 0;
    if (r<L||l>R) return 0;
    if (l>=L&&r<=R){ return node[now].x; }
    int mid=(l+r)>>1, ans=0;
    if (mid>=L) ans+=query(node[now].l, l, mid, L, R);
    if (mid<R) ans+=query(node[now].r, mid+1, r, L, R);
    return ans;
}

struct Query{  //表示能被询问到的区间
    Query(int x=0, int y=0, int d=0){ l=x; r=y; dir=d; }
    int l, r, dir;  //l和r都是主席树上的点
}q[maxn];  //若dir=1表示是上升的,dir=-1表示是下降的
int cntq, latest;

void jump(int x, int y, int flag){  //处理出所有询问区间
    cntq=0; int lca, ox=x, oy=y, lx, ly;
    if (flag&&(x==y||fa[x]==y||fa[y]==x)) return;
    while (top[x]!=top[y]){
        if (dep[top[x]]>dep[top[y]]){
            q[cntq++]=Query(id[top[x]], id[x], 1);
            lx=top[x]; x=fa[top[x]];
        }
        if (top[x]==top[y]) break;
        if (dep[top[x]]<=dep[top[y]]){
            q[cntq++]=Query(id[top[y]], id[y], -1);
            ly=top[y]; y=fa[top[y]];
        }
    }
    if (dep[x]>dep[y]) q[cntq++]=Query(id[y], id[x], 1), lca=y;
    else q[cntq++]=Query(id[x], id[y], -1), lca=x;
    if (!flag) return;
    if (ox==lca){
        if(x==y) jump(ly, fa[oy], 0); else jump(son[ox], fa[oy], 0);
    } else if (oy==lca){
        if (x==y) jump(fa[ox], lx, 0); else jump(fa[ox], son[oy], 0);
    } else jump(fa[ox], fa[oy], 0);
}

//二分查找线段树区间上从左到右第k个未标记点
int div1(int nowb, int nowa, int l, int r, int L, int R, int k){
    if (l==r) return l;
    int mid=(l+r)>>1;
    int lans=query(node[nowb].l, l, mid, L, R)
        -query(node[nowa].l, l, mid, L, R);  //左边有多少标记点
    lans=mid-max(l, L)+1-lans; if (mid<L) lans=0;  //注意max这里是坑!
    if (lans>=k) return div1(node[nowb].l, node[nowa].l, l, mid, L, R, k);
    else return div1(node[nowb].r, node[nowa].r, mid+1, r, L, R, k-lans);
}

//二分查找线段树区间上从右到左第k个未标记点
int div2(int nowb, int nowa, int l, int r, int L, int R, int k){
    if (l==r) return l;
    int mid=(l+r)>>1;
    int rans=query(node[nowb].r, mid+1, r, L, R)
        -query(node[nowa].r, mid+1, r, L, R);  //右边有多少标记点
    rans=min(r, R)-mid-rans; if (mid>=R) rans=0;
    if (rans>=k)  return div2(node[nowb].r, node[nowa].r, mid+1, r, L, R, k);
    else return div2(node[nowb].l, node[nowa].l, l, mid, L, R, k-rans);
}

int solve(int k, int y, int now){  //已知这些顺序的区间问第k个未标记点
    for (int i=0; i<cntq; ++i){
        if (q[i].dir==-1) continue;
        int t=query(rt[now], 1, n, q[i].l, q[i].r)
            -query(rt[y], 1, n, q[i].l, q[i].r);  //整段区间上的标记点个数
        t=q[i].r-q[i].l+1-t;
        if (k<=t) return ori[div2(rt[now], rt[y], 1, n, q[i].l, q[i].r, k)];
        k-=t;
    }
    for (int i=cntq-1; i>=0; --i){  //倒着处理区间
        if (q[i].dir==1) continue;
        int t=query(rt[now], 1, n, q[i].l, q[i].r)
            -query(rt[y], 1, n, q[i].l, q[i].r);
        t=q[i].r-q[i].l+1-t;
        if (k<=t) return ori[div1(rt[now], rt[y], 1, n, q[i].l, q[i].r, k)];
        k-=t;
    }
    return -1;
}

int main(){
    scanf("%d", &n); int t;
    for (int i=1; i<=n; ++i){
        scanf("%d", &t);
        if (!t) root=i; else addedge(t, i);
    }
    dfs1(root, 0, 0); top[root]=root; dfs2(root);
    int op, a, b, c, k, y;
    scanf("%d", &m);
    for (int i=1; i<=m; ++i){
        scanf("%d", &op);
        if (op==1){
            scanf("%d", &c);
            ins(rt[i], rt[i-1], 1, n, id[c]); continue; }
        rt[i]=rt[i-1];
        scanf("%d%d%d%d", &a, &b, &k, &y);
        jump(a, b, 1);
        printf("%d
", solve(k, y, i));  //走y+1到i时刻之间没有被标记的点
    }
    return 0;
}
原文地址:https://www.cnblogs.com/MyNameIsPc/p/9352007.html