Codeforces 938G Shortest Path Queries 线段树分治+并查集+线性基

题意

给出一个连通带权无向图,边有边权,要求资瓷q个操作:
1 x y d在原图中加入一条x到y权值为b的边
2 x y把图中x到y的边删掉
3 x y表示询问x到y的异或最短路
保证任意操作后原图连通无重边自环且操作均合法
n,m,q<=200000

分析

如果没有前两个操作的话,我们就可以把原图的dfs树建出来,那么x到y的异或最短路就是它们在dfs树上的路径再异或上任意个由返祖边组成的环。这个显然可以用线性基来维护。
现在有了加边和删边,那么我们可以用线段树分治来做,把每条边放到O(log)个区间上,然后分治。
设f[x]表示dfs树上x到根的路径异或和,可以通过按秩合并并查集来维护f[x],然后每次退栈的时候撤销即可。
每个分治节点维护一个线性基,每次把线性基复制一遍传给下一层即可。
一开始wa了是因为在并查集合并的时候,并查集上新增那条边的权值应该是f[x] xor f[y] xor 图中新增边的权值,而我少加了一个f[x]。关键是我少加了居然还能过对拍。

代码

#include
#include
#include
#include
#include
#include
#include
#define pb(x) push_back(x)
#define mp(x,y) make_pair(x,y)
using namespace std;

const int N=200005;

typedef pair<int,int> pi;

int n,m,val[N],f[N],size[N],stack[N],top,bas[35][35],bin[35],cnt,tot;
pi qu[N];
struct edge{int u,v,w,s,t;}e[N*2];
mapint> ma;
vector<int> vec[N*4];

int read()
{
    int x=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

int find(int x)
{
    while (f[x]!=x) x=f[x];
    return x;
}

int get_val(int x)
{
    int w=0;
    while (f[x]!=x) w^=val[x],x=f[x];
    return w;
}

void add(int d,int dep)
{
    for (int i=0;iint id=vec[d][i],u=e[id].u,v=e[id].v,x=find(u),y=find(v);
        if (x!=y)
        {
            if (size[x]>size[y]) swap(u,v),swap(x,y);
            f[x]=y;size[y]+=size[x];val[x]=get_val(u)^get_val(v)^e[id].w;stack[++top]=x;
        }
        else
        {
            int w=get_val(u)^get_val(v)^e[id].w;
            for (int i=30;i>=0;i--)
                if (w&bin[i])
                {
                    if (!bas[dep][i]) {bas[dep][i]=w;break;}
                    else w^=bas[dep][i];
                }
        }
    }
}

void del(int tmp)
{
    while (top>tmp)
    {
        int x=stack[top];top--;
        size[f[x]]-=size[x];f[x]=x;val[x]=0;
    }
}

void ins(int d,int l,int r,int x,int y,int id)
{
    if (x>y) return;
    if (l==x&&r==y) {vec[d].pb(id);return;}
    int mid=(l+r)/2;
    if (y<=mid) ins(d*2,l,mid,x,y,id);
    else if (x>mid) ins(d*2+1,mid+1,r,x,y,id);
    else ins(d*2,l,mid,x,mid,id),ins(d*2+1,mid+1,r,mid+1,y,id);
}

void solve(int d,int l,int r,int dep)
{
    int tmp=top;add(d,dep);
    if (l==r)
    {
        int x=qu[l].first,y=qu[l].second,w=get_val(x)^get_val(y);
        for (int i=30;i>=0;i--) if ((w^bas[dep][i])printf("%d\n",w);del(tmp);
        return;
    }
    int mid=(l+r)/2;
    for (int i=0;i<=30;i++) bas[dep+1][i]=bas[dep][i];
    solve(d*2,l,mid,dep+1);
    for (int i=0;i<=30;i++) bas[dep+1][i]=bas[dep][i];
    solve(d*2+1,mid+1,r,dep+1);
    del(tmp);
}

int main()
{
    bin[0]=1;
    for (int i=1;i<=30;i++) bin[i]=bin[i-1]*2;
    n=read();m=read();
    for (int i=1;i<=m;i++)
    {
        int x=read(),y=read(),z=read();
        e[++cnt].u=x;e[cnt].v=y;e[cnt].w=z;
        ma[mp(x,y)]=cnt;e[cnt].s=1;e[cnt].t=-1;
    }
    int q=read();
    for (int i=1;i<=q;i++)
    {
        int op=read(),x=read(),y=read();
        if (op==1)
        {
            int d=read();
            e[++cnt].u=x;e[cnt].v=y;e[cnt].w=d;
            ma[mp(x,y)]=cnt;e[cnt].s=tot+1;e[cnt].t=-1;
        }
        else if (op==2) e[ma[mp(x,y)]].t=tot,ma[mp(x,y)]=0;
        else qu[++tot]=mp(x,y);
    }
    for (int i=1;i<=cnt;i++) ins(1,1,tot,e[i].s,e[i].t==-1?tot:e[i].t,i);
    for (int i=1;i<=n;i++) f[i]=i,size[i]=1;
    solve(1,1,tot,0);
    return 0;
}

你可能感兴趣的:(线段树,并查集,线性基)