【带权并查集】 练习

有一个疑问:
带权并查集中

int find(int x)
{
    if(fa[x]!=x){
        int fx=fa[x];
        fa[x]=find(fx);
        if(flag[x]) flag[fx]=1;
        if(flag[fx]) flag[x]=1;
        dis[x]+=dis[fx];   //dis在这边更新
    }
    return fa[x];
}

那么 如果fa[x]=y ,每一次find(x) ,岂不是dis[x]都会加一遍dis[fx]????
!!! 所以我们会维护每一个块,而我们可以注意到每一个块的root 我们令dis[root]=0;
所以不会存在问题

【poj 1962】Corporative Network(图论–带权并查集 模版题)

/*
N个企业 分成几个网络
每个网络 有一个父亲节点(中心)

两种操作:
1.查询当前时间  机器x到其网络中心的距离
2.设置机器xy 相连 , dis=abs(x-y)%1000;  x所在网络的中心机变为y所在网络的中心机

解法:带权并查集。可以把中心机转换为一个集合(树)的根节点,求距离就是求点到根节点的距离。
于是我们就做并查集的同时维护一个DIS[i]表示点 i 到其根节点的距离。
 */

int fa[20005];
int dis[20005];

int find(int x)
{
    if(fa[x]!=x){
        int fx=fa[x];
        fa[x]=find(fx);
        dis[x]+=dis[fx];   //dis在这边更新
    }
    return fa[x];
}

int main()
{
    freopen("1.txt","r",stdin);
    int t,n;
    char op;
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            fa[i]=i,dis[i]=0;
        while(1){
            cin>>op;
            if(op=='E'){  //询问
                int x;
                scanf("%d",&x);
                find(x);
                printf("%d\n",dis[x]);
            }
            else if(op=='I'){
                int a,b;
                scanf("%d %d",&a,&b);
                fa[a]=b;
                dis[a]=abs(a-b)%1000;
            }
            else
                break;
        }
    }
    return 0;
}

1202: [HNOI2005]狡猾的商人
很经典很经典很经典的题, 一定要弄明白其原理!!!
Description
刁姹接到一个任务,为税务部门调查一位商人的账本,看看账本是不是伪造的。账本上记录了n个月以来的收入情况,其中第i 个月的收入额为Ai(i=1,2,3…n-1,n), 。当 Ai大于0时表示这个月盈利Ai 元,当 Ai小于0时表示这个月亏损Ai 元。所谓一段时间内的总收入,就是这段时间内每个月的收入额的总和。 刁姹的任务是秘密进行的,为了调查商人的账本,她只好跑到商人那里打工。她趁商人不在时去偷看账本,可是她无法将账本偷出来,每次偷看账本时她都只能看某段时间内账本上记录的收入情况,并且她只能记住这段时间内的总收入。 现在,刁姹总共偷看了m次账本,当然也就记住了m段时间内的总收入,你的任务是根据记住的这些信息来判断账本是不是假的。
Input
第一行为一个正整数w,其中w < 100,表示有w组数据,即w个账本,需要你判断。每组数据的第一行为两个正整数n和m,其中n < 100,m < 1000,分别表示对应的账本记录了多少个月的收入情况以及偷看了多少次账本。接下来的m行表示刁姹偷看m次账本后记住的m条信息,每条信息占一行,有三个整数s,t和v,表示从第s个月到第t个月(包含第t个月)的总收入为v,这里假设s总是小于等于t。
Output
包含w行,每行是true或false,其中第i行为true当且仅当第i组数据,即第i个账本不是假的;第i行为false当且仅当第i组数据,即第i个账本是假的。
思路:
如果账本为真,则每一条语句都必须是真的
所以我们应当视全部都为真,如果出现一条语句和前面的冲突,则该账本为假

我们采用带权并查集维护其状态。 并且根据是否与现有状态冲突 来判断真假。

int fa[20005];
ll dis[20005];
int flag;
int find(int x)
{
    if(fa[x]!=x){
        int fx=fa[x];
        fa[x]=find(fx);
        dis[x]+=dis[fx];   //dis在这边更新
    }
    return fa[x];
}
void Union(int x,int y,int d){
    int fx=find(x),fy=find(y);
    if(fx!=fy){
        fa[fx]=fy;
        dis[fx]=dis[y]+d-dis[x];  // 这里要注意:  x---fx----y-(+dis[y]=fy)
    }
    else
        if(dis[x]-dis[y] != d)   // x----y-----(fxfy)   默认x<y
            flag=0;
}
int main()
{
    freopen("1.txt","r",stdin);
    int t,n;
    scanf("%d",&t);
    while(t--){
        int n,m,s,t,v;
        scanf("%d %d",&n,&m);
        for(int i=0;i<=n;i++)
            fa[i]=i,dis[i]=0;
        flag=1;
        for(int i=1;i<=m;i++){
            scanf("%d %d %d",&s,&t,&v);  // sum[t]-sum[s-1]=v;
            if(!flag) continue;
            Union(s-1,t,v);
        }
        flag?printf("true\n"):printf("false\n");
    }
    return 0;
}

我之前以为这么做有问题,有一组数据
1
5 4
1 5 15
2 3 5
1 2 3
4 5 -1
输出为 true
1 2 3 4 5
11 -8 13 0 -1 而且有多解, 因为4.5 的状态 并没有确定下来。
这里写图片描述

POJ1988 简单题:
n 个管道
p 次操作,两种操作

m move X所在的栈,到 Y 所在栈 的上面 x 所属的栈1,y所属的栈2 变成一个栈

c count 有多少个管道 同属于X所在的栈,但是在X下面
保证不会出现把自己移动到自己上面的操作


int fa[30005];
int dis[30005];   // 到根节点的距离。
int back[30005]; // 整个块 最下面一个的编号
int find(int x)
{
    if(fa[x]!=x){
        int fx=fa[x];
        fa[x]=find(fx);
        dis[x]+=dis[fx];   //dis在这边更新
    }
    return fa[x];
}
void Union(int x,int y){
    int fx=find(x),fy=find(y);
    if(fx!=fy){
        int p=back[fx];
        fa[fy]=back[p];  //fy 父亲是x
        dis[fy]= 1;  // dis[p]+1 ?  不会啊,看一下find 里面有+dis[fx];
        back[fx]=back[fy];
//      printf("back %d =%d\n",fx,back[fy]);
    }
}
int main()
{
    freopen("1.txt","r",stdin);
    int t,n=30000;
    char  op;
    for(int i=0;i<=n;i++){
        fa[i]=i,dis[i]=0,back[i]=i;
    }
    scanf("%d",&t);
    while(t--){
        cin>>op;
        int x,y;
        if(op=='M'){
            scanf("%d %d",&x,&y);
            Union(x,y);
        }
        else{
            scanf("%d",&x);
            int fx=find(x);
            find(back[fx]);
//          printf("%d %d = %d\n",dis[back[fx]], dis[x], dis[back[fx]]- dis[x]);
            printf("%d\n",dis[back[fx]]- dis[x]);
        }
    }
    return 0;
}

POJ 1984 一道不错的带权并查集练手题 :
分别告诉你 x点 的哪个方向多少距离的地方有另外一个点。
问:x、y之间的曼哈顿距离

输入:
x y l f :x y 路的长度和方向,y在x 的f方向

k 个问题
x y t :x y 在第t个操作执行完之后问

这个题目描述 我真醉了
8 7
1 2 1 S
3 4 5 S
5 6 8 S
7 8 2 S
3 2 3 N
7 6 13 N
5 4 7 N
6
1 3 1
1 4 5
3 4 2
4 5 7
1 8 6
1 8 7

put:
-1
9
5
7
-1
39


int fa[40005];
int dis_x[40005];   // 到根节点的x距离。 左为负,右为正
int dis_y[40005];   // 到根节点的x距离。 下为负,上为正


int find(int x)
{
    if(fa[x]!=x){
        int fx=fa[x];
        fa[x]=find(fx);
        dis_x[x]+=dis_x[fx];   //dis在这边更新
        dis_y[x]+=dis_y[fx];
    }
    return fa[x];
}
void Union(int x,int y){
    int fx=find(x),fy=find(y);
    if(fx!=fy){

    }
}
struct node{
    int x,y,l;
    char f;
}pin[40005];
struct ndoe{
    int x,y,t;
    int i;
}q[10005];
int ans[10005];
bool cmp(ndoe a,ndoe y){
    return a.t<y.t;
}
int main()
{
    freopen("1.txt","r",stdin);
    int t,n,m;
    char op;
    scanf("%d %d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d %d %d",&pin[i].x,&pin[i].y,&pin[i].l);
        cin>>op;
        pin[i].f=op;
    }
    int k;
    scanf("%d",&k);
    for(int i=1;i<=k;i++){
        scanf("%d %d %d",&q[i].x,&q[i].y,&q[i].t);
        q[i].i=i;
    }
    sort(q+1,q+k+1,cmp);
    int index1=0;
    int index2=1;
    // 我们把 树的根 以及每一个不连通的块 的root 设置为 各自出现的第一个点 ,
    for(int i=1;i<=n;i++)
        fa[i]=i,dis_x[i]=dis_y[i]=0;
    while(index1<m){
        index1++;
        op=pin[index1].f;
        int x,y,l;
        x=pin[index1].x;
        y=pin[index1].y;
        l=pin[index1].l;
        int fx=find(x),fy=find(y);
//      printf("i=%d  %d %d\n",index1,fx,fy);
        if(op=='N'){   // yx 的上方
            if(fx!=fy){
                fa[fx]=y;   //fx 的父亲变为y
                dis_x[fx]= -dis_x[x]  ;   //  y-----x----fx
                dis_y[fx]= -dis_y[x] + l;
            }
        }
        if(op=='S'){// yx 的下方
            if(fx!=fy){
                fa[fx]=y;
                dis_x[fx]= -dis_x[x]  ;
                dis_y[fx]= -dis_y[x] - l;
            }
        }
        if(op=='W'){
            if(fx!=fy){
                fa[fx]=y;
                dis_x[fx]= -dis_x[x] - l ;
                dis_y[fx]= -dis_y[x] ;
            }
        }
        if(op=='E'){
            if(fx!=fy){
                fa[fx]=y;
                dis_x[fx]= -dis_x[x] + l ;
                dis_y[fx]= -dis_y[x]  ;
            }
        }
        while(q[index2].t==index1){  //问了个问题
//          printf("i=%d  %d %d\n",index1,index2,q[index2].t);
            x=q[index2].x;
            y=q[index2].y;
            int fx=find(x);
            int fy=find(y);   // 在这个题里面 我们可以知道  已知的root dis_x=dis_y=0  ,所以不存在重复计算
            if(fx!=fy){
//              printf("-1\n");
                ans[q[index2].i]=-1;
            }
            else{
                int cnt= abs(dis_x[x]-dis_x[y]) + abs(dis_y[x]-dis_y[y]); // x---y---root , x--rot--y
//              printf("%d\n",ans);
                ans[q[index2].i]=cnt;
            }
            index2++;
            if(index2>k)
                break;
        }
    }
    for(int i=1;i<=k;i++)
        printf("%d\n",ans[i]);
    return 0;
}

Codeforces Round #396 (Div. 2)
http://codeforces.com/contest/766/problem/D
算得上是简单题吧,随便处理一下就可以1A
题意:
一共给你N个单词,M个关系,Q次关系查询。
关系: op x y 1 表示x和y是同义词, 2 表示x和y是反义词
如果这个关系是正确的 输出yes ,如果这个关系和上面的某个正确的关系矛盾,输出no
然后q行
q 询问: 同义词输处1,反义词输出2,不知道输出3


int fa[100005];
int dis[100005];  // 到根节点的x距


int find(int x)
{
    if(fa[x]!=x){
        int fx=fa[x];
        fa[x]=find(fx);
        dis[x]+=dis[fx];   //dis在这边更新 ,所以dis[root]一定=0
        dis[x]%=2; // 0 同,1反
    }
    return fa[x];
}
void Union(int x,int y){
    int fx=find(x),fy=find(y);
    if(fx!=fy){
        fa[fx]=fy;
        // 我们需要将x和y并为同类,即dis[x] =dis[y]
        if(dis[x]==dis[y]) // x---fx-fy----y
            dis[fx]=0;
        else
            dis[fx]=1;
    }
}
map<string ,int>mp;
char s[25];
char x[25],y[25];
int main()
{
    freopen("1.txt","r",stdin);
    int t,n,m,q;
    scanf("%d %d %d",&n,&m,&q);
    string str;
    for(int i=1;i<=n;i++){
        cin>>str;
        mp[str]=i;
        fa[i]=i;
        dis[i]=0;
    }
    int op;
    string x,y;
    while(m--){
        scanf("%d",&op);
        cin>>x>>y;  // 如果T,就换成char
        int p1=mp[x],p2=mp[y];
        int fx=find(p1),fy=find(p2);
//      printf("%d %d %d %d\n",p1,p2,fx,fy);
        if(op==1){  //  同义词
            if(fx!=fy){
                Union(p1,p2);
                printf("YES\n");
            }
            else{
                if(dis[p1]!=dis[p2]){
                    printf("NO\n");
                }
                else
                    printf("YES\n");
            }
        }
        else{   //反义词
            if(fx==fy){
                if(dis[p1]==dis[p2])
                    printf("NO\n");
                else
                    printf("YES\n");
            }
            else{ //将 p1,p2 变成反义词 fx!=fy    x-----fx--fy-----y
                fa[fx]=fy ;
                if(dis[p1]==dis[p2])
                    dis[fx]=1;
                else
                    dis[fx]=0;
                printf("YES\n");
            }
        }
    }
    while(q--){
        cin>>x>>y;
        int p1=mp[x],p2=mp[y];
        int fx=find(p1),fy=find(p2);
        if(fx!=fy){
            printf("3\n");
        }
        else{
            if(dis[p1]!=dis[p2])
                printf("2\n");
            else
                printf("1\n");
        }
    }
    return 0;
}

bzoj4602

猜你喜欢

转载自blog.csdn.net/qq_24664053/article/details/70261488