第十四届华中科技大学程序设计竞赛决赛 A Beauty of Trees(带权并查集)


题意:
n个数的序列,值都为止,有m次记录,每一次记录给你一个区间和该区间的异或和。
每一次记录正误的标准是如果该记录与之前的记录无矛盾,则正确,否则错误。
让你输出每一条错误记录的标号。

解析:

任意两个已知区间,只要有一个边界重合,就可以扩展出新的已知区间。

这样我们就可以将所有边界重合的区间归为一类,因为这样这一类里面任意两个边界的值我们都可以得到


我这里的思路是,对于每一次询问进行一遍dfs.一旦这一次的询问的某一个边界已知,那么我们就将所有已知区间求出来
,即例如[a,b],[a,c]已知,那么我们就可以求出(b,c]。按照这样已知dfs下去,直到无法得到已知区间或所得到的已知区间与

之前的矛盾为止。并且,这里如果与之前的矛盾,还要将之前通过这个错误的已知区间得出来的所有已知区间还原。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long int ll;
const int MAXN = 1e5+100;
 
int bac[MAXN];
int bef[MAXN];
ll valc[MAXN],valf[MAXN];
 
int dfs(int a,int b,ll c)
{
 
    if(bac[a]==0&&bef[b]==0)
    {
        bac[a]=b;
        bef[b]=a;
        valc[a]=valf[b]=c;
        return 1;
    }
    else if(bac[a]==b||bef[b]==a)
    {
        if(valc[a]==c) return 1;
        else return 0;
    }
    else if(bac[a]!=0)
    {
        int ano=bac[a];
        ll past=valc[a];
        int flag;
        if(b>ano) 
        {
            flag=dfs(ano+1,b,past^c);
        }
        else
        {
            valc[a]=c;
            bac[a]=b;
            ll bc=valf[b];
            int bt=bef[b];
            int ccl=bef[ano];
            valf[b]=c;
            bef[b]=a;
            bef[ano]=0;
            flag=dfs(b+1,ano,past^c);
            if(!flag)
            {
                valc[a]=past;
                bac[a]=ano;
                valf[b]=bc;
                bef[b]=bt;
                bef[ano]=ccl;
            }
        }
        if(flag) return 1;
        else
        {
            return 0;
        }
    }
    else if(bef[b]!=0)
    {
        int ano=bef[b];
        ll past=valf[b];
 
        int flag;
        if(a>ano) 
        {
            ll bc=valc[a];
            int bt=bac[a];
            int ccl=bac[ano];
            valf[b]=valc[a]=c;
            bac[a]=b;
            bef[b]=a;
            bac[ano]=0;
            flag=dfs(ano,a-1,past^c);
            if(!flag)
            {
                valf[b]=past;
                valc[a]=bc;
                bac[a]=bt;
                bef[b]=ano;
                bac[ano]=ccl;
            }
        }
        else flag=dfs(a,ano-1,past^c);
        if(flag) return 1;
        else
        {
            return 0;
        }
    }
    else
    {
        return 1;
    }
}
int ans[MAXN];
int main()
{
    int n,m;
    int cnt=0;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int a,b;
        ll c;
        scanf("%d%d%lld",&a,&b,&c);
 
        int tmp=dfs(a,b,c);
        if(!tmp) ans[cnt++]=i;
    }
    if(cnt==0)
    {
        printf("-1\n");
        return 0;
    }
    for(int i=0;i<cnt;i++)
    {
        printf("%d\n",ans[i]);
    }
    return 0;
}
而网上还有一些更好的做法
用带权并查集来做。将每一个点自己归为一类,一旦知道某一个区间[a,b],那么就将a这一类归并到b这一类中。
因为a类的定义为{x∈A,[x,a]已知或[a,x]已知},这样[a,b]已知后,[x,b]||[b,x]都已知x∈A,同理b类的值到a也都已知
那么a,b里面的值就可以归为一类了。所以每一次查询,如果他们在同一类,那么该区间的值是一定已知的,就比较一下是否正确就行。如果他们不在同一类,则将他们归为一类。


val[i]=(i,x]||(x,i]的异或和,x={i所在类的祖先}

权值并查集模板点击打开链接

#include<cstdio>
#include<cstring>

typedef long long int ll;
const int MAXN = 1e5+100;
int fac[MAXN];
ll val[MAXN];


int find_(int a)
{
    if(fac[a]==a) return a;
    int nex=fac[a];
    fac[a]=find_(nex);
    val[a]^=val[nex];
    return fac[a];
}

int Merge(int x,int y,ll z)
{
    int fx=find_(x);
    int fy=find_(y);
    if(fx==fy)
    {
        if((val[x]^val[y])!=z)
        {
            return 0;
        }
    }
    else
    {
        fac[fx]=fy;
        val[fx]=val[x]^val[y]^z;
    }
    return 1;
}


int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) fac[i]=i;
    int flag=0;
    for(int i=1;i<=m;i++)
    {
        int u,v;
        ll z;
        scanf("%d%d%lld",&u,&v,&z);
        --u;
        int tmp=Merge(u,v,z);
        if(tmp==0) printf("%d\n",i),flag=1;
    }
    if(!flag) printf("-1\n");
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_37025443/article/details/80398595