美文网首页
「数据结构进阶」练习

「数据结构进阶」练习

作者: 云中翻月 | 来源:发表于2019-03-07 22:06 被阅读0次
    0x49「数据结构进阶」练习

    4901 关押罪犯
    分析题意可知,存在两个监狱,即两个相互对立的集合,囚犯之间的仇恨就是关系,就是图论中的带权无向边。因此采用扩展域并查集维护关系。考虑贪心原理,为了使答案尽量小,可以将所有矛盾关系从大到小排序,依次考虑每条边的情况。首先保证边权大的边的两端顶点在不同集合,若产生矛盾则意味着分配失败,当前冲突必然发生。
    代码如下

    /*
    
    */
    #define method_1
    #ifdef method_1
    /*
    
    */
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<set>
    #include<map>
    #include<queue>
    #include<stack>
    #include<vector>
    #include<cstring>
    #include<cstdlib>
    #include<iomanip>
    #define D(x) cout<<#x<<" = "<<x<<"  "
    #define E cout<<endl
    using namespace std;
    typedef long long ll;
    typedef pair<int,int>pii;
    const int maxn=100000+5; //题目数据范围有误 不是20000
    const int INF=0x3f3f3f3f;
    int n,m;
    struct node{
        int a,b,c;
        bool operator<(const node &h)const{return c>h.c;}
    }p[maxn];
    int f[maxn<<1];
    int find(int x){
        return f[x]==x?x:f[x]=find(f[x]);
    }
    int main() {
        ios::sync_with_stdio(false);
        //freopen("关押罪犯.in","r",stdin);
        cin>>n>>m;
        for(int i=1;i<=m;i++) cin>>p[i].a>>p[i].b>>p[i].c;
        sort(p+1,p+m+1);
        for(int i=1;i<=n*2;i++) f[i]=i; 
        for(int i=1;i<=m;i++){
            int x=p[i].a,y=p[i].b;
            if(find(x)==find(y)){ //少写find(x+n)==find(y+n)没事 
                cout<<p[i].c;
                return 0;
            }
            f[find(x)]=find(y+n); //必须两条都要有 
            f[find(y)]=find(x+n); //合并时一侧为1~n的节点 另一侧为n+1~2n的节点 
        }
        cout<<0;
        return 0;
    }
    #endif
    #ifdef method_2
    /*
    
    */
    
    #endif
    #ifdef method_3
    /*
    
    */
    
    #endif
    

    POJ1417 True Liars
    分析题意可知,说真话的人和说假话的人是两个对立的集合,因此考虑用扩展域/边带权并查集维护关系。具体到实现上,我们发现,如果回答为yes,那么两个人要么都说谎,要么都说真话。如果回答为no,那么必然一人说真话,一人说假话。处理完所有关系后,我们的到的图论模型是若干个联通块(假设有m个联通块,第i个联通块属于说真话集合的有a[i][0]个人,说假话集合的有a[i][1]个人)。这时考虑题目中的问题,要求判断能否区分联通块使得一部分联通块共有p个节点,剩下的联通块共有q个节点。这等价于求在m个联通块中选择若干个联通块,使得这些联通块中共有p个人的方案数。若方案数是1,那么说明存在唯一的可行方案。这就是01背包。当然,本题在实现时,我无法压缩掉背包的第一维,暂时原因不明。
    代码如下

    /*
    
    */
    #define method_1
    #ifdef method_1
    /*
    拓展域并查集
    */
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<set>
    #include<map>
    #include<queue>
    #include<stack>
    #include<vector>
    #include<cstring>
    #include<cstdlib>
    #include<iomanip>
    #define D(x) cout<<#x<<" = "<<x<<"  "
    #define E cout<<endl
    using namespace std;
    typedef long long ll;
    typedef pair<int,int>pii;
    const int maxn=600+5;
    const int INF=0x3f3f3f3f;
    int n,p1,p2,u,v,f[maxn*2],d[maxn][maxn],pre[maxn][maxn],a[maxn][2],used[maxn*2],val[maxn*2];
    vector<int>b[maxn][2],ans;
    string s;
    int find(int x){
        return f[x]==x?x:f[x]=find(f[x]);
    }
    int main() {
        ios::sync_with_stdio(false);
        //freopen("True Liars.in","r",stdin);
    //  freopen("True Liars.out","w",stdout);
        while(cin>>n>>p1>>p2) {
            if(n==0&&p1==0&&p2==0) break;
            memset(d,0,sizeof(d));
            memset(pre,0,sizeof(pre));
            memset(used,0,sizeof(used));
            memset(a,0,sizeof(a));
            for(int i=1;i<=600;i++) b[i][0].clear(),b[i][1].clear();
            ans.clear();
            d[0][0]=1; //背包第一维不能省略 原因未知 
            for(int i=1; i<=2*(p1+p2); i++) f[i]=i;
            for(int i=1; i<=n; i++) {
                cin>>u>>v>>s;
                int u1=find(u),u2=find(u+p1+p2),v1=find(v),v2=find(v+p1+p2); //注意这里要先全find出来再合并
                //不能写成u=find(u) 然后f[u]=v;f[u+p1+p2]=v+p1+p2 因为 u+p1+p2没有find 
                if(s=="yes") {
                    f[u1]=v1;
                    f[u2]=v2;
                }
                if(s=="no") {
                    f[u1]=v2;
                    f[v1]=u2;
                }
            }
            int cnt=0;
            for(int i=1; i<=(p1+p2); i++){
                if(used[i]) continue;
                cnt++;
                int temp=find(i);
                for(int j=1;j<=(p1+p2);j++){
                    if(temp==find(j)){
                        used[j]=1;
                        a[cnt][0]++;
                        b[cnt][0].push_back(j);
                    }
                    if(temp==find(j+p1+p2)){
                        used[j]=1;
                        a[cnt][1]++;
                        b[cnt][1].push_back(j);
                    }
                }
            } 
    //      D(cnt);E;
            /*
            for(int i=1; i<=cnt; i++) {
                for(int j=0; j<=1; j++) {
                    cout<<a[i][j]<<" ";
                }
                cout<<endl;
            }
            */
            for(int i=1; i<=cnt; i++) {
                if(a[i][1]==0&&a[i][0]==0) continue;
                for(int j=p1; j>=0; j--) {
                    if(j>=a[i][0]&&d[i-1][j-a[i][0]]) {
                        d[i][j]+=d[i-1][j-a[i][0]];
                        pre[i][j]=j-a[i][0];
                    }
                    if(j>=a[i][1]&&d[i-1][j-a[i][1]]) {
                        d[i][j]+=d[i-1][j-a[i][1]];
                        pre[i][j]=j-a[i][1];
                    }
                }
            }
    //      cout<<d[p1]<<endl;
            if(d[cnt][p1]!=1){
                cout<<"no"<<endl;
                continue;
            }
            int t=p1;
            for(int i=cnt;i>=1;i--){
                if(a[i][1]==0&&a[i][0]==0) continue;
                int temp=t-pre[i][t];
                if(temp==a[i][0]){
                    for(int j=0;j<a[i][0];j++) ans.push_back(b[i][0][j]);
                }
                else{
                    for(int j=0;j<a[i][1];j++) ans.push_back(b[i][1][j]);
                }
                t=pre[i][t];
            }
            sort(ans.begin(),ans.end());
            for(int i=0;i<ans.size();i++){
                cout<<ans[i]<<endl;
            }
            cout<<"end"<<endl;
        }
        return 0;
    }
    #endif
    

    POJ3667 Hotel
    区间问题,试图用线段树求解。考虑一个区间的状态(即区间标记),有三种,全空,全满,其他情况。我们用一个mark表示这三种状态,对应的mark取值分别为0,1,-1。
    接下来考虑一个区间中可能产生答案的可能情况,有三种,空区间紧挨左边界,空区间在中间,空区间紧挨右边界。为了方便我们表示答案和生成答案,我们给线段树对应区间节点设置如下几个信息:l,r,mark,lsum,rsum,dat。l,r表示区间左右端点,mark表示区间状态,lsum,rsum表示仅靠区间左侧/右侧的最长空区间,dat表示整个区间的最长空区间。
    其中mark的更新方式类似于线段树中的延迟标记,每次query和change的时候向下传递标记。

    if(tr[rt].mark!=-1){
        tr[rt<<1].mark=tr[rt<<1|1].mark=tr[rt].mark;
        tr[rt].mark=-1;
        tr[rt<<1].update();
        tr[rt<<1|1].update();
    }
    

    lsum,rsum,dat的计算则互相推导而出

    tr[rt].dat=max(tr[rt<<1].dat,max(tr[rt<<1].rsum+tr[rt<<1|1].lsum,tr[rt<<1|1].dat));
    tr[rt].lsum=tr[rt<<1].lsum,tr[rt].rsum=tr[rt<<1|1].rsum;
    if(tr[rt<<1].dat==tr[rt<<1].r-tr[rt<<1].l+1) tr[rt].lsum+=tr[rt<<1|1].lsum;
    if(tr[rt<<1|1].dat==tr[rt<<1|1].r-tr[rt<<1|1].l+1) tr[rt].rsum+=tr[rt<<1].rsum;
    

    完整代码如下

    /*
    
    */
    #define method_1
    #ifdef method_1
    /*
    https://www.cnblogs.com/scau20110726/archive/2013/05/07/3065418.html
    */
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<set>
    #include<map>
    #include<queue>
    #include<stack>
    #include<vector>
    #include<cstring>
    #include<cstdlib>
    #include<iomanip>
    #define D(x) cout<<#x<<" = "<<x<<"  "
    #define E cout<<endl
    using namespace std;
    typedef long long ll;
    typedef pair<int,int>pii;
    const int maxn=50000+5;
    const int INF=0x3f3f3f3f;
    struct SegmentTree{
        int l,r,mark,lsum,rsum,dat;
        //mark=0 区间全空 mark=1区间全满 mark=-1子区间未更新(区间未全满的状态难以计算 且会被子问题组成 所以不标记)
        void update(){
            lsum=rsum=dat=mark?0:r-l+1;
        } 
    }tr[maxn<<2];
    int n,m;
    void build(int rt,int l,int r){
        tr[rt].l=l,tr[rt].r=r,tr[rt].mark=0,tr[rt].lsum=tr[rt].rsum=tr[rt].dat=r-l+1;
        if(l==r) return;
        int mid=l+r>>1;
        build(rt<<1,l,mid),build(rt<<1|1,mid+1,r);
    }
    int query(int rt,int x){
        if(tr[rt].l==tr[rt].r&&x==1) return tr[rt].l;
        if(tr[rt].mark!=-1){
            tr[rt<<1].mark=tr[rt<<1|1].mark=tr[rt].mark;
            tr[rt].mark=-1;
            tr[rt<<1].update();
            tr[rt<<1|1].update();
        }
        if(tr[rt<<1].dat>=x) return query(rt<<1,x);
        if(tr[rt<<1].rsum+tr[rt<<1|1].lsum>=x) return tr[rt<<1].r-tr[rt<<1].rsum+1;
        if(tr[rt<<1|1].dat>=x) return query(rt<<1|1,x);
        return 0; 
    }
    void change(int rt,int l,int r,int v){
        if(tr[rt].l>=l&&tr[rt].r<=r){
            tr[rt].mark=v;
            tr[rt].update();
            return;
        }
        if(tr[rt].mark!=-1){
            tr[rt<<1].mark=tr[rt<<1|1].mark=tr[rt].mark;
            tr[rt].mark=-1;
            tr[rt<<1].update();
            tr[rt<<1|1].update();
        }
        int mid=tr[rt].l+tr[rt].r>>1;
        /*
        if(r<=mid) change(rt<<1,l,r,v);
        else if(l>mid) change(rt<<1|1,l,r,v);
        else{
            change(rt<<1,l,mid,v);
            change(rt<<1|1,mid+1,r,v);
        } 
        */
        if(l<=mid) change(rt<<1,l,r,v);
        if(r>mid) change(rt<<1|1,l,r,v);
        tr[rt].dat=max(tr[rt<<1].dat,max(tr[rt<<1].rsum+tr[rt<<1|1].lsum,tr[rt<<1|1].dat));
        tr[rt].lsum=tr[rt<<1].lsum,tr[rt].rsum=tr[rt<<1|1].rsum;
        if(tr[rt<<1].dat==tr[rt<<1].r-tr[rt<<1].l+1) tr[rt].lsum+=tr[rt<<1|1].lsum;
        if(tr[rt<<1|1].dat==tr[rt<<1|1].r-tr[rt<<1|1].l+1) tr[rt].rsum+=tr[rt<<1].rsum;
    }
    int main() {
        ios::sync_with_stdio(false);
        //freopen("Hotel.in","r",stdin);
        cin>>n>>m;
        build(1,1,n);
        int op,x,d;
        while(m--){
            cin>>op>>x;
            if(op==1){
                int ans=query(1,x);
                cout<<ans<<endl;
                if(ans) change(1,ans,ans+x-1,1);
            }
            else{
                cin>>d;
                change(1,x,x+d-1,0);
            }
        }
        return 0;
    }
    #endif
    #ifdef method_2
    /*
    
    */
    
    #endif
    #ifdef method_3
    /*
    
    */
    
    #endif
    

    POJ1177 Picture
    有三种方法可行,法一法二参见这个博客
    http://www.cnblogs.com/scau20110726/archive/2013/04/13/3018687.html
    https://www.cnblogs.com/scau20110726/archive/2013/04/13/3018702.html
    第三种方法,只计算周长的一半,然后将答案乘2就是结果。具体实现中,用cx和cy数组记录每一段上的扫描线被覆盖的次数,若次数为0则表示到达了右边界/上边界,并记入答案。
    但是这个博客里头的代码并不能ac本题,原因在于若出现矩形的边重叠,则会产生重复计数,这就需要所有竖线/横线在排序的过程中保证靠右/靠上的矩形的左/下边界,在排序后处于靠左/靠下的矩形的右/上边界的前面。stl中的sort函数无法实现,需要使用qsort函数计算。原因暂时不明。
    方法三代码如下

    /*
    必须要用qsort 用sort会错误 排序方式不一样 
    */
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<set>
    #include<map>
    #include<queue>
    #include<stack>
    #include<vector>
    #include<cstring>
    #include<cstdlib>
    #include<iomanip>
    #define D(x) cout<<#x<<" = "<<x<<"  "
    #define E cout<<endl
    using namespace std;
    typedef long long ll;
    typedef pair<int,int>pii;
    const int maxn=10000+5;
    const int INF=0x3f3f3f3f;
    int n,numx=0,numy=0,ans=0,cx[maxn],cy[maxn];
    int rawx[maxn],rawy[maxn];
    struct node {
        int p,st,ed,f;
        /*
        bool operator<(const node &h)const {
            return p<h.p;
        }
        */
    } linex[maxn],liney[maxn];//x:横线 y:竖线 
    int cmp(const void *a,const void *b){
        return ((node *)a)->p-((node *)b)->p;
    }
    //int lshx[maxn],lshy[maxn];
    int valx(int y) {
        return lower_bound(rawx+1,rawx+numx+1,y)-rawx;
    }
    int valy(int y){
        return lower_bound(rawy+1,rawy+numy+1,y)-rawy;
    }
    int main() {
        ios::sync_with_stdio(false);
        //freopen("Picture.in","r",stdin);
        cin>>n;
        int X1,X2,Y1,Y2;
        for(int i=1;i<=n;i++){
            cin>>X1>>Y1>>X2>>Y2;
            linex[2*i-1].p=Y1,linex[2*i-1].st=X1,linex[2*i-1].ed=X2,linex[2*i-1].f=1;
            linex[2*i].p=Y2,linex[2*i].st=X1,linex[2*i].ed=X2,linex[2*i].f=-1;
            liney[2*i-1].p=X1,liney[2*i-1].st=Y1,liney[2*i-1].ed=Y2,liney[2*i-1].f=1;
            liney[2*i].p=X2,liney[2*i].st=Y1,liney[2*i].ed=Y2,liney[2*i].f=-1;
            rawx[++numx]=X1,rawx[++numx]=X2;
            rawy[++numy]=Y1,rawy[++numy]=Y2;
        }
        sort(rawx+1,rawx+2*n+1);
        numx=unique(rawx+1,rawx+numx+1)-rawx-1;
        sort(rawy+1,rawy+2*n+1);
        numy=unique(rawy+1,rawy+numy+1)-rawy-1;
        /*
        for(int i=1;i<=2*n;i++){
            lshy[valy(liney[i].st)]=lshy[valy(liney[i].ed)]=1;
        }
        */
        qsort(linex+1,2*n,sizeof(linex[0]),cmp);
        qsort(liney+1,2*n,sizeof(liney[0]),cmp);
        /*
        for(int i=1;i<=2*n;i++){
            cout<<"liney[i].st:"<<liney[i].st<<"  liney[i].ed:"<<liney[i].ed<<endl;
        }
        */ 
        /*
        for(int i=1;i<=numy;i++){
            if(lshy[i]){
                lshy[i]=i;
            }
        }
        */
        /*
        for(int i=1;i<=2*n;i++){
            cout<<"liney[i].st:"<<liney[i].st<<"  liney[i].ed:"<<liney[i].ed<<endl;
            liney[i].st=lshy[valy(liney[i].st)];
            liney[i].ed=lshy[valy(liney[i].ed)];
            cout<<"liney[i].st:"<<liney[i].st<<"  liney[i].ed:"<<liney[i].ed<<endl;
        }
        */
        for(int i=1;i<=2*n;i++){
            for(int j=valx(linex[i].st);j<valx(linex[i].ed);j++){
                cx[j]+=linex[i].f;
                if(cx[j]==0){
                    //cout<<rawx[j+1]-rawx[j]<<" ";
                    ans+=rawx[j+1]-rawx[j];
                }
            }
        }
        for(int i=1;i<=2*n;i++){
        //  for(int j=liney[i].st;j<liney[i].ed;j++){
            for(int j=valy(liney[i].st);j<valy(liney[i].ed);j++){
                cy[j]+=liney[i].f;
                if(cy[j]==0){
                    //cout<<rawy[j+1]-rawy[j]<<" ";
                    ans+=rawy[j+1]-rawy[j];
                }
            }
        }
        cout<<ans*2;
        return 0;
    }
    

    4907 作诗
    考虑类似“蒲公英”的做法,预处理出任意两个块之间每种文字的出现次数(第i块到第j块的答案记为f[i][j]),再在回答询问的时候额外朴素计算两端不完整的块的贡献即可。
    代码如下

    /*
    
    */
    #define method_1
    #ifdef method_1
    /*
    由于空间限制 所以原本用cnt[i][j][k]表示 第j块到第k块中种类i的出现次数会爆空间
    但由于可以前缀和相减
    所以 cnt[i][j]表示前j块中种类i的出现次数即可 每次询问相减就行
    sum[i]表示目前种类i出现的次数 
    */
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<set>
    #include<map>
    #include<queue>
    #include<stack>
    #include<vector>
    #include<cstring>
    #include<cstdlib>
    #include<iomanip>
    #define D(x) cout<<#x<<" = "<<x<<"  "
    #define E cout<<endl
    using namespace std;
    typedef long long ll;
    typedef pair<int,int>pii;
    const int maxn=1e5+5;
    const int INF=0x3f3f3f3f;
    int n,c,m,num,sz,blo[maxn],a[maxn],cnt[320][maxn],f[320][320],ans=0,sum[maxn],l[320],r[320],vis[maxn],t=0;
    //由于会有多次询问 但每次最多改变O(根号n)个v中的元素 所以用t来表示这次访问 
    void pre(){
        num=int(sqrt(n));
        sz=n/num;
        for(int i=1;i<=n;i++) blo[i]=(i-1)/num+1;
        for(int i=1;i<=num;i++) l[i]=(i-1)*sz+1,r[i]=i*sz;//l[i],r[i]数组记录第i个块的左右端点 
        if(r[num]!=n) l[num+1]=r[num]+1,r[++num]=n;
        for(int i=1;i<=num;i++){
            for(int j=1;j<=c;j++) cnt[i][j]=cnt[i-1][j];
            for(int j=l[i];j<=r[i];j++) cnt[i][a[j]]++;
        }
        for(int i=1;i<=num;i++){
            int temp=0;
            memset(sum,0,sizeof(sum));
            for(int j=i;j<=num;j++){
                for(int k=l[j];k<=r[j];k++){
                    sum[a[k]]++;
                    if((sum[a[k]]&1)==0) temp++; //出现超过1次算 
                    else if(sum[a[k]]>1) temp--; //出现一次不算 出现超过1次且为奇数次要-- 抵消偶数次的计算 
                }
                f[i][j]=temp;
            }
        }
    }
    int solve(int x,int y){
         int L=blo[x],R=blo[y];//blo[i]表示第i个元素所在块的编号 
         int ans=f[L][R];
         for(int i=l[L];i<x;i++){
            if(vis[a[i]]<t) vis[a[i]]=t,sum[a[i]]=cnt[R][a[i]]-cnt[L-1][a[i]];
            sum[a[i]]--;
            if(sum[a[i]]&1) ans--;//现在是奇数 说明曾是偶数 
            else if(sum[a[i]]>1) ans++; //现在是次数超过1次的偶数 说明曾是奇数 
         }
         for(int i=y+1;i<=r[R];i++){
            if(vis[a[i]]<t) vis[a[i]]=t,sum[a[i]]=cnt[R][a[i]]-cnt[L-1][a[i]];
            sum[a[i]]--;
            if(sum[a[i]]&1) ans--;//现在是奇数 说明曾是偶数 
            else if(sum[a[i]]>1) ans++; //现在是次数超过1次的偶数 说明曾是奇数  
         }
         return ans;
    }
    int main() {
        ios::sync_with_stdio(false);
        freopen("作诗.in","r",stdin);
        cin>>n>>c>>m;
        for(int i=1;i<=n;i++) cin>>a[i];
        pre();
        while(m--){
            t++;
            int L,R;
            cin>>L>>R;
            L=(L+ans)%n+1,R=(R+ans)%n+1;
            if(L>R) swap(L,R);
            ans=solve(L,R);
            cout<<ans<<endl;
        }
        return 0;
    }
    #endif
    

    4908 Race
    树上路径统计问题,使用点分治算法,由于数据中可能存在链状数据,所以每次以子树重心为根进行点分治。
    和「数据结构进阶」例题中的tree不同,由于不是计数问题,而是取最值问题,所以不能直接用容斥的方式统计。
    但是可以用method_2的方式用ans数组记录可行情况,依次扫描求解,这样就可以使用容斥原理了 。
    两种方式的代码如下(ps:method_2是容斥原理,method_3则额外维护了每个点所在的子树根节点,目前method_2状态为AC,而method_3状态为95分,原因未知)

    #define method_3
    #ifdef method_2
    /*
    容斥原理做法
    ac 
    */
    #include<stdio.h>
    #include<string.h>
    #include<algorithm>
    #define INF 0x3f3f3f3f
    using namespace std;
    const int maxn = 200010;
    struct node {
        int to,next,cost;
    } e[maxn*2];
    struct data {
        int l,e;
    } dis[maxn];
    int n,m,K,tot,head[maxn],size[maxn],sz,total,vis[maxn],root,p,ans[maxn*2];
    
    void insert(int u, int v, int w) {
        e[++tot].to=v;
        e[tot].next=head[u];
        head[u]=tot;
        e[tot].cost=w;
    }
    
    void getroot(int u, int f) {
        int mx=0;
        size[u]=1;
        for (int i=head[u],v; i; i=e[i].next) {
            if (vis[v=e[i].to] || v==f) continue;
            getroot(v,u);
            size[u]+=size[v];
            mx=max(mx,size[v]);
        }
        mx=max(mx,total-size[u]);
        if (mx<sz) sz=mx,root=u;
    }
    
    void getdis(int u, int f, int len, int num) {
        dis[++p].l=len;
        dis[p].e=num;
        for (int i=head[u],v; i; i=e[i].next) {
            if (vis[v=e[i].to] || v==f) continue;
            getdis(v,u,len+e[i].cost,num+1);
        }
    }
    
    bool cmp(data a, data b) {
        if (a.l==b.l) return a.e<b.e;
        return a.l<b.l;  /////
    }
    
    void count(int u, int len, int num, int f) {
        p=0;
        getdis(u,0,len,num); //这里要将len和num传下去。。
        int l=1,r=p;
        sort(dis+1,dis+1+p,cmp);
        while (l<=r) { //注意这里要用<=,WA了几发
            while (l<r && dis[l].l+dis[r].l>K) r--;
            for (int k=r; dis[l].l+dis[k].l==K; k--) ans[dis[l].e+dis[k].e]+=f;
            l++;
        }
    }
    
    void work(int u) {
        total=size[u]?size[u]:n;
        sz=INF;
        getroot(u,0);
        u=root;
        vis[u]=1;
        count(u,0,0,1);
        for (int i=head[u],v; i; i=e[i].next) {
            if (vis[v=e[i].to]) continue;
            count(v,e[i].cost,1,-1);
            work(v);
        }
    }
    
    int main() {
        scanf("%d%d", &n, &K);
        for (int i=1,u,v,w; i<n; i++) scanf("%d%d%d", &u, &v, &w),u++,v++,insert(u,v,w),insert(v,u,w);
        work(1);
        for (int i=1; i<n; i++) if (ans[i]) {
                printf("%d\n", i);
                return 0;
            }
        puts("-1");
        return 0;
    }
    #endif
    #ifdef method_3
    /*
    维护每个点所在子树的做法 
    有一个点wa 九十五分 
    */
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<set>
    #include<map>
    #include<queue>
    #include<stack>
    #include<vector>
    #include<cstring>
    #include<cstdlib>
    #include<iomanip>
    #define D(x) cout<<#x<<" = "<<x<<"  "
    #define E cout<<endl
    using namespace std;
    typedef long long ll;
    typedef pair<int,int>pii;
    const int maxn=200000+5;
    const int INF=0x3f3f3f3f;
    int n,k,head[maxn],root,mxson[maxn],s[maxn],cnt=0,ans=INF,mins,tot=0,vis[maxn];
    struct node {
        int from,to,v;
    } edge[maxn<<1];
    struct node1{
        int d,c,b;
        bool operator<(const node1 &h)const{return d<h.d||(d==h.d&&c<h.c);}
    }dis[maxn];
    void add(int from,int to,int v) {
        edge[++cnt].to=to,edge[cnt].v=v,edge[cnt].from=head[from],head[from]=cnt;
    }
    void getdis(int x,int len,int num,int belong) {
        vis[x]=1; //由于点分治之后 x节点的所有父系节点都统计结束了 所以需要用vis而不用fa
        dis[++tot].d=len,dis[tot].c=num,dis[tot].b=belong;
        for(int i=head[x]; i; i=edge[i].from) {
            int y=edge[i].to,v=edge[i].v;
            if(vis[y]) continue;
            getdis(y,len+v,num+1,belong);
        }
        vis[x]=0;
    }
    void getroot(int S,int x) { //求以x节点为根,大小为S的子树,存在全局变量root中
        s[x]=1,vis[x]=1;
        int max_part=0;
        for(int i=head[x]; i; i=edge[i].from) {
            int y=edge[i].to;
            if(vis[y]) continue;
            getroot(S,y);
            s[x]+=s[y];
            max_part=max(max_part,s[y]);
        }
        max_part=max(max_part,S-s[x]);
        if(max_part<mins) {
            mins=max_part;
            root=x;
        }
        vis[x]=0;
    }
    void divide(int S,int x) { //以x节点为根,大小为S的子树上经过根节点的路径统计
        mins=S;
        getroot(S,x);
        tot=0;
        vis[root]=1;
        dis[++tot].d=0,dis[tot].b=root,dis[tot].c=0;
        for(int i=head[root]; i; i=edge[i].from) {
            int y=edge[i].to,v=edge[i].v;
            if(vis[y]) continue;
            getdis(y,v,1,y);
        }
        sort(dis+1,dis+tot+1);
        /*
        for(int i=1;i<=tot;i++){
            cout<<dis[i].b<<" "<<dis[i].c<<" "<<dis[i].d<<endl;
        }
        cout<<endl<<endl;
        */ 
        int L=1,R=tot;
        while(L<=R) {
            while(L<R&&dis[L].d+dis[R].d>k) R--;
            while(dis[L].d+dis[R].d==k&&L<R) {
                if(dis[L].b!=dis[R].b) ans=min(ans,dis[L].c+dis[R].c);
                R--;
            }
            L++;
        }
        int now=root;
        //由于root是全局变量 所以会在divide递归的过程中不断变化 因此这里要用now变量暂时保存这一层的root  
        for(int i=head[now]; i; i=edge[i].from) {
            int y=edge[i].to;
            if(vis[y]) continue;
            divide(s[y],y);
        }
    }
    int main() {
        ios::sync_with_stdio(false);
        //freopen("Race.in","r",stdin);
        cin>>n>>k;
        int from,to,v;
        for(int i=1; i<=n-1; i++) {
            cin>>from>>to>>v;
            add(from,to,v),add(to,from,v);
        }
        ans=n;
        divide(n,0);
        if(ans==n) cout<<-1;
        else cout<<ans;
        return 0;
    }
    #endif
    

    4909 营业额统计
    平衡树维护前驱后继的模板题,手写平衡树或者用set都能AC。
    代码如下

    /*
    
    */
    #define method_1
    #ifdef method_1
    /*
     
    */
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<set>
    #include<map>
    #include<queue>
    #include<stack>
    #include<vector>
    #include<cstring>
    #include<cstdlib>
    #include<iomanip>
    #define D(x) cout<<#x<<" = "<<x<<"  "
    #define E cout<<endl
    using namespace std;
    typedef long long ll;
    typedef pair<int,int>pii;
    const int maxn=32767+5;
    const int INF=0x3f3f3f3f;
    int n,ans=0,tot=0,root;
    struct node {
        int l,r,dat,val;
    } a[maxn];
    int New(int val) {
        a[++tot].val=val,a[tot].dat=rand();
        return tot;
    }
    void Build() {
        a[1].r=2,root=1;
        New(-INF*2),New(INF*2);
    }
    void zig(int &p) {
        int q=a[p].l;
        a[p].l=a[q].r,a[q].r=p;
        p=q;
    //  Update(a[p].r),Update(p);
    }
    void zag(int &p) {
        int q=a[p].r;
        a[p].r=a[q].l,a[q].l=p;
        p=q;
    //  Update(a[p].l),Update(p);
    }
    void insert(int &p,int val) {
        if(p==0) {
            p=New(val);
            return;
        }
        if(val<=a[p].val) {
            insert(a[p].l,val);
            if(a[p].dat<a[a[p].l].dat) zig(p);
        } else {
            insert(a[p].r,val);
            if(a[p].dat<a[a[p].r].dat) zag(p);
        }
    }
    int GetPre(int val) {
        int p=root,ans=-INF;
        while(p) {
            if(val==a[p].val)return a[p].val;
            if(val>a[p].val) ans=max(ans,a[p].val),p=a[p].r;
            else p=a[p].l;
        }
        return ans;
    }
    int GetNext(int val) {
        int p=root,ans=INF;
        while(p) {
            if(val==a[p].val)return a[p].val;
            if(val<a[p].val) ans=min(ans,a[p].val),p=a[p].l;
            else p=a[p].r;
        }
        return ans;
    }
    int main() {
    //  ios::sync_with_stdio(false);
        //freopen("营业额统计.in","r",stdin);
        cin>>n;
        int x;
        Build();
        for(int i=1; i<=n; i++) {
            if(scanf("%d",&x)==EOF)x=0; //这题数据有问题 有一个点数据不全 要加上这句话 
            int temp1=GetPre(x),temp2=GetNext(x);
            if(i==1) ans+=abs(x);
            else ans+=min(x-temp1,temp2-x);
            insert(root,x);
        }
        cout<<ans;
        return 0;
    }
    #endif
    #ifdef method_2
    /*
    
    */
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<set>
    #include<map>
    #include<queue>
    #include<stack>
    #include<vector>
    #include<cstring>
    #include<cstdlib>
    #include<iomanip>
    #define D(x) cout<<#x<<" = "<<x<<"  "
    #define E cout<<endl
    using namespace std;
    typedef long long ll;
    typedef pair<int,int>pii;
    const int maxn=32767+5;
    const int INF=0x3f3f3f3f;
    int n,ans=0;
    multiset<int>s;
    multiset<int>::iterator it; 
    int main() {
        ios::sync_with_stdio(false);
        //freopen("营业额统计.in","r",stdin);
        cin>>n;
        int x;
        for(int i=1; i<=n; i++) {
            cin>>x;
            s.insert(x);
            int temp=INF;
            if(i==1) {ans+=abs(x);continue;}
            it=s.find(x);
            if(it!=s.begin()){
                --it;
                temp=min(temp,x-(*it));
                it++;
            }
            it++;
            if(it!=s.end()){
                temp=min(temp,(*it)-x);
            }
            ans+=temp;
        }
        cout<<ans;
        return 0;
    }
    #endif
    

    POJ3580 SuperMemo
    这种虐区间的题目往往是Splay的菜,然而我只会treap,所以我学学用fhq_treap即无旋treap/可持久化treap来做。然而,我目前还没有看懂fhq_treap,因此这道题暂时坑着。
    4911 Mokia
    cdq分治裸题,注意如下两点:
    1 用结构体中的pos变量来记录操作的编号,做到询问的顺序输出。
    2 树状数组维护一维坐标,所以树状数组的所有操作范围都是这一维下标的坐标范围。
    代码如下

    /*
    
    */
    #define method_1
    #ifdef method_1
    /*
    
    */
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<set>
    #include<map>
    #include<queue>
    #include<stack>
    #include<vector>
    #include<cstring>
    #include<cstdlib>
    #include<iomanip>
    #define D(x) cout<<#x<<" = "<<x<<"  "
    #define E cout<<endl
    using namespace std;
    typedef long long ll;
    typedef pair<int,int>pii;
    const int maxn=160000+40000+5;
    const int maxp=2000000+5;
    const int INF=0x3f3f3f3f;
    int s,w,opt,n=0,ans[maxn],c[maxp];//树状数组维护一维坐标,所以树状数组的所有操作范围都是这一维下标的坐标范围。所以是maxp而不是maxn
    struct node{
        int op,x,y,val,sum,pos;
        bool operator<(const node &h)const{return x<h.x||(x==h.x&&op<h.op);}
    }a[maxn],t[maxn];
    void add(int x,int v){
        for(int i=x;i<=w;i+=i&-i) c[i]+=v; //注意i的上界是w 不是n 
    }
    int ask(int x){
        int temp=0;
        for(int i=x;i>=1;i-=i&-i) temp+=c[i];
        return temp;
    }
    void cdq(int l,int r){
        if(l>=r) return;
        int mid=l+r>>1;
        cdq(l,mid),cdq(mid+1,r);
        int p=0;
        for(int i=l;i<=r;i++){
            if((a[i].op==1&&i<=mid)||(a[i].op==2&&i>mid)){
                t[++p]=a[i],t[p].pos=i;
            }
        }
        sort(t+1,t+p+1);
        for(int i=1;i<=p;i++){
            if(t[i].op==1) add(t[i].y,t[i].val);
            else a[t[i].pos].sum+=ask(t[i].y);
        }
        for(int i=1;i<=p;i++){
            if(t[i].op==1){
                add(t[i].y,-t[i].val);
            }
        }
    }
    int main() {
        ios::sync_with_stdio(false);
    //  freopen("Mokia.in","r",stdin);
        cin>>s>>w;
        int x,y,x2,y2,temp;
        while(cin>>opt&&opt!=3){
            if(opt==1){
                cin>>x>>y>>temp;
                a[++n].op=1,a[n].x=x,a[n].y=y,a[n].val=temp;
            }
            else{
                cin>>x>>y>>x2>>y2;
                ans[++n]=abs(x2-x+1)*abs(y2-y+1)*s;
                a[n].op=2,a[n].x=x-1,a[n].y=y-1,a[n].sum=0;
                a[++n].op=2,a[n].x=x-1,a[n].y=y2,a[n].sum=0;
                a[++n].op=2,a[n].x=x2,a[n].y=y-1,a[n].sum=0;
                a[++n].op=2,a[n].x=x2,a[n].y=y2,a[n].sum=0;
            }
        }
        cdq(1,n);
        for(int i=1;i<=n;i++){
            if(a[i].op==2){
                int temp=ans[i];
                temp+=a[i].sum;
                temp-=a[++i].sum;
                temp-=a[++i].sum;
                temp+=a[++i].sum;
                cout<<temp<<endl;
            }
        }
        return 0;
    }
    #endif
    #ifdef method_2
    /*
    
    */
    
    #endif
    #ifdef method_3
    /*
    
    */
    
    #endif
    

    4912 Meteors
    分析题意可知,需要求解每个国家最少需要接受多少轮陨石雨。如果只求一个国家,可以考虑使用二分答案的做法。现在由于求解的国家数较多,所以考虑使用基于值域的整体二分。
    与此同时,为了避免区间修改导致的复杂度退化,我们利用差分序列将区间修改改为单点修改,并用树状数组维护。
    具体实现上,用如下代码可以做到处理区间的顺时针和逆时针问题。

    void change(int i,int num){
        if(a[i].l>a[i].r) add(1,a[i].w*num);
        add(a[i].l,a[i].w*num);
        add(a[i].r+1,-a[i].w*num);
    }
    

    完整代码如下

    /*
    
    */
    #define method_1
    #ifdef method_1
    /*
    
    */
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<set>
    #include<map>
    #include<queue>
    #include<stack>
    #include<vector>
    #include<cstring>
    #include<cstdlib>
    #include<iomanip>
    #define D(x) cout<<#x<<" = "<<x<<"  "
    #define E cout<<endl
    using namespace std;
    typedef long long ll;
    typedef pair<int,ll>pii; //first记录答案的顺序 
    const int maxn=3e5+5;
    const ll INF=0x3f3f3f3f3f3f3fll;
    ll c[maxn];
    int n,m,k,temp,ans[maxn];
    pii q[maxn],lq[maxn],rq[maxn]; 
    vector<int>e[maxn];
    struct node{
        ll l,r,w;
    }a[maxn];
    void add(int x,ll v){
        for(int i=x;i<=m;i+=i&-i) c[i]+=v;
    }
    ll ask(int x){
        ll sum=0;
        for(int i=x;i>=1;i-=i&-i) sum+=c[i];
        return sum;
    }
    void change(int i,int num){
        if(a[i].l>a[i].r) add(1,a[i].w*num);
        add(a[i].l,a[i].w*num);
        add(a[i].r+1,-a[i].w*num);
    }
    void solve(int lval,int rval,int st,int ed){
        if(st>ed) return;
        if(lval==rval){
            for(int i=st;i<=ed;i++){
                ans[q[i].first]=lval;
    //          cout<<i<<" "<<q[i].first<<endl; 两个不一样 
            }
            return;
        }
        int mid=lval+rval>>1,lt=0,rt=0;
        for(int i=lval;i<=mid;i++) change(i,1); //由于所有操作都是同一个类型的(区间修改),所以这里的做法比整体二分的模板有所简化
        for(int i=st;i<=ed;i++){
            ll tot=0;
            for(int j=0;j<e[q[i].first].size();j++){
                tot+=ask(e[q[i].first][j]);
                if(tot>q[i].second) break;
            }
            if(tot>=q[i].second) lq[++lt]=q[i];
            else q[i].second-=tot,rq[++rt]=q[i];
        }
        for(int i=lval;i<=mid;i++) change(i,-1);
        for(int i=1;i<=lt;i++) q[i+st-1]=lq[i];
        for(int i=1;i<=rt;i++) q[i+lt+st-1]=rq[i];
        solve(lval,mid,st,st+lt-1);
        solve(mid+1,rval,st+lt,ed);
    }
    int main() {
        ios::sync_with_stdio(false);
        freopen("Meteors.in","r",stdin);
        cin>>n>>m;
        for(int i=1;i<=m;i++) cin>>temp,e[temp].push_back(i);
        for(int i=1;i<=n;i++) q[i].first=i,cin>>q[i].second;
        cin>>k;
        for(int i=1;i<=k;i++) cin>>a[i].l>>a[i].r>>a[i].w;
        a[++k].l=1,a[k].r=m,a[k].w=INF;
        solve(1,k,1,n);
        for(int i=1;i<=n;i++){
            if(ans[i]==k) cout<<"NIE"<<endl;
            else cout<<ans[i]<<endl;
        }
        return 0;
    }
    #endif
    #ifdef method_2
    /*
    
    */
    
    #endif
    #ifdef method_3
    /*
    
    */
    
    #endif
    

    4913 Fotile模拟赛L
    根据区间异或和的特性,将区间异或转化为异或前缀和后的两个端点异或的最大值。直接对于每个询问枚举端点求解,时间复杂度为O(m*n^{2})
    考虑优化,假设左右端点没有范围限制,我们可以将每个数拆分二进制,从高位到低位插入一棵可持久化trie。然后枚举右端点r,在可持久化trie中从高位到低位,贪心查找每一位与a[r](此时a数组是异或前缀和数组)相反的指针,如果存在则这一位(第i位)就对答案产生了1<<i的贡献。
    若存在左右端点的限制,就需要对每个trie上的节点记录一个变量rpos表示这个节点最靠右的节点编号。每次在trie上贪心查找的时候,只考虑rpos>=l的情况即可。
    这样做的时间复杂度O(m*nlogn)
    考虑继续优化,我们可以分块预处理答案,设b[i][j]表示第i块中,第i*l+1到j的a数组的异或和的最大值,其中l为块数。
    最终时间复杂度O(n\sqrt{n}logn)
    代码如下

    /*
    
    */
    #define method_1
    #ifdef method_1
    /*
    
    */
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<set>
    #include<map>
    #include<queue>
    #include<stack>
    #include<vector>
    #include<cstring>
    #include<cstdlib>
    #include<iomanip>
    #define D(x) cout<<#x<<" = "<<x<<"  "
    #define E cout<<endl
    using namespace std;
    typedef long long ll;
    typedef pair<int,int>pii;
    const int maxn=12000+5;
    const int maxm=6000+5;
    const int maxlogn=32+5;
    const int maxsqrtn=120+5;
    const int INF=0x3f3f3f3f;
    int trie[maxn*maxlogn][2],rpos[maxn*maxlogn]={-1},h[maxn],n,m,tot=0,a[maxn],x,y,ans=0,b[maxsqrtn][maxn];
    //注意rpos要初始化为-1 
    void insert(int last,int now,int k){
        int p;
        h[k]=p=++tot,rpos[p]=k; //h数组记录每个历史时期trie的根节点 
        for(int i=30;i>=0;i--){
            int j=(now>>i)&1;
            trie[p][j^1]=trie[last][j^1]; //复制原先的树 
            trie[p][j]=++tot;
            p=tot,rpos[p]=k;
            last=trie[last][j];
        }
    }
    int ask(int l,int r,int x){
        int p=h[r],ans=0;
        for(int i=30;i>=0;i--){
            int j=((x>>i)&1)^1;
            if(rpos[trie[p][j]]>=l) ans|=1<<i;
            else j^=1;
            p=trie[p][j];
        }
        return ans;
    }
    int main() {
    //  ios::sync_with_stdio(false);
        //freopen("Fotile模拟赛L.in","r",stdin);
    //  freopen("Fotile模拟赛L.out","w",stdout);
        cin>>n>>m;
        int t=(int)sqrt(n);
        int l=n/t+(n%t!=0);
        for(int i=1;i<=n;i++) scanf("%d",&a[i]),a[i]^=a[i-1];
        insert(h[0],a[0],0);
        for(int i=1;i<=n;i++) insert(h[i-1],a[i],i);
        for(int i=0;i<t;i++){
            for(int j=i*l+1;j<=n;j++){
                b[i][j]=max(b[i][j-1],ask(i*l,j-1,a[j]));
                //b[i][j]表示第i块中,第i*l+1到j的a数组的异或和的最大值 
                //注意由于转化为了异或前缀和 所以[l,r]的最大异或和等价于[l-1,r]中找两个异或前缀和的点异或最大 
                //因此这里是i*l而不是i*l+1 
            }
        } 
        int i; //见了鬼了 这个i要定义在这里 定义在循环里会慢20倍 所以 有的编译器根本不允许在for里定义 
        while(m--){
            scanf("%d%d",&x,&y);
            //l = min ( ((x+lastans) mod N)+1 , ((y+lastans) mod N)+1 )
            x=(x+ans%n)%n+1,y=(y+ans%n)%n+1; //不能用上面那个公式 否则会错 
            if(x>y) swap(x,y);
            x--; //理由同上 转化为前缀和的过程中左端点要-1 
        //  cout<<x<<" "<<y<<endl;
            int p=x/l+(x%l!=0); //p为x所处的块的下一个块的编号 
            ans=p*l<y?b[p][y]:0;//p*l是x所处的块的下一个块的右端点 
            //p*l<y 表示x和y不在同一个块中  
            for(i=x,p=min(p*l,y);i<=p;i++){ 
            //暴力处理x到x所处的块的右端点/y的内容 最多处理根号n个元素 
                ans=max(ans,ask(x,y,a[i]));
            }
            printf("%d\n",ans);
        }
        return 0;
    }
    #endif
    

    4914 可持久化并查集
    为了实现可持久化,我们的并查集中不能直接使用路径压缩,那样会破化原本的树形结构(其实貌似也可以,但我不会)。于是为了防止并查集的复杂度退化,我们使用按秩合并的方式优化并查集的find操作(这里的“秩”使用的是节点的深度,而不是节点子树的大小)。同时为了能够回复到历史状态,我们用一个可持久化线段树来维护并查集的f数组和每个节点的deep数组即可。
    具体到实现上,我们采用引用递归的方式建树,因此不能用结构体来保存可持久化线段树。每次合并时将深度小的节点所在子树合并到深度大的节点后部。第i个操作需要将并查集回复到第k个操作时的状态时,只要作如下操作即可。

    root[i]=root[k];
    

    时间复杂度O(nlogn)
    空间复杂度O(nlogn)
    代码如下

    /*
    
    */
    #define method_1
    #ifdef method_1
    /*
    
    */
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<set>
    #include<map>
    #include<queue>
    #include<stack>
    #include<vector>
    #include<cstring>
    #include<cstdlib>
    #include<iomanip>
    #define D(x) cout<<#x<<" = "<<x<<"  "
    #define E cout<<endl
    using namespace std;
    typedef long long ll;
    typedef pair<int,int>pii;
    const int maxn=2e5+5;
    const int maxlogn=40+5;
    const int INF=0x3f3f3f3f;
    int n,m,op;
    /*
    由于存在引用递归赋值 所以不用结构体
    struct SegmentTree{
        int lc,rc;
    }tr[maxn*maxlogn<<2];
    */
    int root[maxn],f[maxn*maxlogn],tot,deep[maxn*maxlogn],ls[maxn*maxlogn<<2],rs[maxn*maxlogn<<2];
    void build(int &k,int l,int r) { //注意k是引用 这个函数会生成一颗线段树 
        if(!k) k=++tot;
        if(l==r) {
            f[k]=l;
            return;
        }
        int mid=(l+r)/2;
    //  build(tr[k].lc,l,mid);
    //  build(tr[k].rc,mid+1,r);
        build(ls[k],l,mid);
        build(rs[k],mid+1,r);
    }
    void modify(int j,int &i,int l,int r,int pos,int val) { //注意i是引用 这个函数会复制上一个状态j的线段树,然后增加一条链(即第i个状态) 
        //modify(root[i-1],root[i],1,n,f[r1],f[r2]); 
        i=++tot;
        if(l==r) {
            f[i]=val,deep[i]=deep[j]; //将r1接到r2后面 
            return;
        }
    //  tr[i].lc=tr[j].lc,tr[i].rc=tr[j].rc;
        ls[i]=ls[j],rs[i]=rs[j]; //复制节点 
        int mid=(l+r)/2;
    //  if(pos<=mid) modify(tr[j].lc,tr[i].lc,l,mid,pos,val);
    //  else modify(tr[j].rc,tr[j].rc,mid+1,r,pos,val);
        if(pos<=mid) modify(ls[j],ls[i],l,mid,pos,val);
        else modify(rs[j],rs[i],mid+1,r,pos,val);
    }
    int query(int k,int l,int r,int x) {
        if(l==r) return k;
        int mid=(l+r)/2;
    //  if(x<=mid) return query(tr[k].lc,l,mid,x);
    //  else return query(tr[k].rc,mid+1,r,x);
        if(x<=mid) return query(ls[k],l,mid,x);
        else return query(rs[k],mid+1,r,x);
    }
    void add(int i,int l,int r,int pos) {
        if(l==r) {
            deep[i]++;
            return;
        }
        int mid=(l+r)/2;
    //  if(pos<=mid) add(tr[i].lc,l,mid,pos);
    //  else add(tr[i].rc,mid+1,r,pos);
        if(pos<=mid) add(ls[i],l,mid,pos);
        else add(rs[i],mid+1,r,pos);
    }
    int find(int k,int x) {
        int t=query(k,1,n,x);
        if(f[t]==x) return t;
        return find(k,f[t]); //一定要return啊 
    }
    int main() {
        ios::sync_with_stdio(false);
        //freopen("可持久化并查集.in","r",stdin);
        cin>>n>>m;
        int ans=0,a,b,k;
        build(root[0],1,n);
        for(int i=1; i<=m; i++) {
            cin>>op;
            if(op==1) {
                cin>>a>>b;
                a^=ans,b^=ans;
                root[i]=root[i-1];
                int r1=find(root[i],a),r2=find(root[i],b);
                if(f[r1]==f[r2]) continue;
                if(deep[r1]>deep[r2]) swap(r1,r2);//按秩合并 
                modify(root[i-1],root[i],1,n,f[r1],f[r2]); //有合并操作 则将i-1个操作时的状态复制过来 然后再做改变 
                if(deep[r1]==deep[r2]) add(root[i],1,n,f[r2]);
                //若深度相同 任选一个节点为父节点 然后将另一个节点所在子树连接过来 这个子树上所有节点的深度deep+1即可 
            }
            if(op==2) {
                cin>>k;
                k^=ans;
                root[i]=root[k];
            }
            if(op==3) {
                cin>>a>>b;
                a^=ans,b^=ans;
                root[i]=root[i-1]; //没有改变结构 只需要将i-1个操作时的状态复制过来即可 
                int r1=find(root[i],a),r2=find(root[i],b);
                if(f[r1]==f[r2]) ans=1;
                else ans=0;
                cout<<ans<<endl;
            }
        }
        return 0;
    }
    #endif
    

    相关文章

      网友评论

          本文标题:「数据结构进阶」练习

          本文链接:https://www.haomeiwen.com/subject/xbshpqtx.html