「数据结构进阶」练习

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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354

推荐阅读更多精彩内容