2022”蔚来杯“牛客多校第八场 I. Equivalence in Connectivity(哈希,线段树分治)

题意:

  先给出一个n个点m条边的无向图,随后进行(k-1)次操作,每次操作选择一个之前存在的图,并增加/删除一条边以形成新的图,最后让你对这k张图(包括最开始那张)的连通性进行分类并输出,对于两张图,从一张图的每个点出发能到达的其他点与另一张图一样(例如从1出发都能到达2,3),即认为这两张图连通性相同。

思路:

1.首先我们要表达一个图的连通性,那么我们考虑到用哈希,这里介绍一种哈希方法:

    对图的每个点赋一个随机值,对一个连通块,它的值为块内所有点的值的异或和,一张图的哈希值为所有连通块的值的和,这样我们就可以用哈希表示一张图了。在这里我使用了双哈希解决冲突。

2.先不考虑选择一个之前存在的图这个操作,只对当前的这张图进行加边/删边操作,如果有学过线段树分治的话,会发现这个问题跟线段树分治的板题很像,完全可以用线段树分治解决,我们一边分治一边更新当前的哈希值,分治到叶子节点就用哈希计算答案即可。

3.现在考虑对于选择一个之前存在的图这个操作,首先对于这种回溯操作,我们可以离线把所有操作建成一颗树,比如第i次操作是选择了之前的第j张图进行加边/删边,那么我们就连一条j向i的边。当我们对i点进行加边操作的时候,那么很明显,在还没考虑删除的时候,它会存在于i的整颗子树中;同理,如果我们的j点是对i点删除一条边得到的,那么在j的整颗子树中,都不会有那条被删的边。

4.如果是在数组上进行加边/删边操作,那么我们就可以用线段树分治解决,但操作在树上我们要怎么做呢?答案是把树上操作转换为在数组上操作,我们对树做一个dfn序,那么一颗子树的dfn序是连续的,所以我们对整颗子树加边/删边就相当于在一个区间内操作。

5.先考虑加边操作,我们直接对那个边存在的区间操作即可;对于删边操作则比较复杂,删边在大的树中把一整颗子树的影响去掉,相当于把区间中间扣掉了一块,因此我们用set存每条边的存在区间,加边就把[l,r]加进set。删边[l,r]时,我们先找到包含[l,r]的大区间[L,R],接着我们把[l,r]从[L,R]中删去,即把[L,R]分裂成[L,l-1]和[r,R+1],最后把操作好的区间一一加进去线段树即可。

写的很抽象代码:

#include<bits/stdc++.h>
#define ac return 0
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define endl "\n"
string yes="Yes\n";
string no="No\n";
const int N=2e5+100;
const int MOD=1e9+7;

struct segt
{
    int l,r;
    vector<int>t;
}tr[4*N];

struct qq
{
    int l;
    string s;
    int u,v;
}q[N];

int n,m,k,cnt;
int dfn[N],rnk[N],tot,siz[N],dfrnk[N];
map<pair<ll,ll> ,vector<int> >ans;
map<pair<int,int> ,int>mp;
struct node
{
	int l,r;
	int id;
	bool operator < (const node &a) const {
		return l > a.l;
	}
};


set<node>se[N];

struct edge
{
    int u,v;
}e[N];

vector<int>g[N];

void build(int u,int l,int r)
{
    tr[u]={l,r};
    tr[u].t.clear();
    if(l==r)
    {
        return;
    }
    int mid=l+r>>1;
    build(2*u,l,mid);
    build(2*u+1,mid+1,r);
}

void modify(int u,int l,int r,int id)
{
    if(tr[u].l>=l&&tr[u].r<=r)
    {
        tr[u].t.push_back(id);
        return;
    }
    int mid=tr[u].l+tr[u].r>>1;
    if(l<=mid)
        modify(2*u,l,r,id);
    if(r>mid)
        modify(2*u+1,l,r,id);
}

int parent[N],idx;
ll val[N],sum,val2[N],sum2;

struct sta
{
    int u,v;
    bool isadd;
    int valu,valv;
    int valu2,valv2;
}st[N];

int found(int p)
{
    while(p!=parent[p])
    p=parent[p];
    return parent[p];
}

void merge1(int u,int v)
{
    int fu=found(u),fv=found(v);
    if(fu==fv)
        return;
    if(rnk[fu]>rnk[fv])
        swap(fu,fv);
    st[++idx]={fu,fv,rnk[fu]==rnk[fv],val[fu],val[fv],val2[fu],val2[fv]};
    //fu更矮
    parent[fu]=fv;
    sum-=val[fu]+val[fv];
    val[fv]^=val[fu];
    sum+=val[fv];
    sum2-=val2[fu]+val2[fv];
    val2[fv]^=val2[fu];
    sum2+=val2[fv];
    if(rnk[fu]==rnk[fv])
        rnk[fv]++;
}

void query(int u)
{
    bool ok=1;
    int top=idx;
    int i,j,x,y,id;
    for(i=0;i<tr[u].t.size();i++)
    {
        id=tr[u].t[i];
        x=e[id].u,y=e[id].v;
       merge1(x,y);
    }
        if(tr[u].l==tr[u].r)
           {
           //	cout<<sum<<endl;
               ans[{sum,sum2}].push_back(dfrnk[tr[u].l]);
           }
        else
        {
            query(2*u);
            query(2*u+1);
        }
    while(idx>top)
    {
        int x=st[idx].u,y=st[idx].v;
        sum-=val[y];
        val[x]=st[idx].valu,val[y]=st[idx].valv;
        sum+=val[x]+val[y];
        sum2-=val2[y];
        val2[x]=st[idx].valu2,val2[y]=st[idx].valv2;
        sum2+=val2[x]+val2[y];
        if(st[idx].isadd)
            rnk[y]--;
        parent[x]=x;
        idx--;
    }
}

void dfs(int now)
{
    siz[now]=1;
    dfn[now]=++tot;
    dfrnk[tot]=now;
 for(auto u:g[now])
    {
        dfs(u);
        siz[now]+=siz[u];
    }

}

typedef set<node>::iterator IT;

void solve()
{
    int i,j,u,v,l,r;
    tot=cnt=idx=0;
    sum=0,sum2=0;
    cin>>k>>n>>m;
    build(1,1,k);
    for(i=1;i<=n;i++)
    {
        parent[i]=i;
        rnk[i]=1;
        val[i]=rand();
        val2[i]=rand();
        while(val2[i]==val[i])
            val2[i]=rand();
        sum+=val[i];
    }
    for(i=1;i<=k;i++)
    {
        g[i].clear();
        dfrnk[i]=siz[i]=dfn[i]=0;
    }
    tot=0;
    cnt=0;
    mp.clear();
    for(i=1;i<=m;i++)
    {
        cin>>u>>v;
        mp[{u,v}]=++cnt;
        e[cnt].u=u,e[cnt].v=v;
        se[cnt].clear();
        se[cnt].insert({1,k,cnt});
    }
    ans.clear();
    for(i=2;i<=k;i++)
    {
        string s;
        cin>>l>>s>>u>>v;
        q[i].l=l;
        q[i].s=s;
        q[i].u=u;
        q[i].v=v;
        g[l].push_back(i);
        if(!mp[{u,v}])
            {
                mp[{u,v}]=++cnt;
                e[cnt].u=u,e[cnt].v=v;
            se[cnt].clear();
            }
    }
    dfs(1);
    for(i=2;i<=k;i++)
    {
        string s;
        s=q[i].s;
        l=q[i].l,u=q[i].u,v=q[i].v;
            int now=mp[{u,v}];
        if(s[0]=='a')
        {
           se[now].insert({dfn[i],dfn[i]+siz[i]-1,now});
        }
        else
        {
            l=dfn[i],r=dfn[i]+siz[i]-1;
            IT it = se[now].lower_bound((node){l,0,0});
          
   
            int ll=(*it).l,rr=(*it).r;
       //     cout<<l<<" "<<r<<" "<<ll<<" "<<rr<<endl;
            se[now].erase(it);
            if(ll<=l-1)
            se[now].insert({ll,l-1,now});
            if(r+1<=rr)
            se[now].insert({r+1,rr,now});
        }
    }
    for(i=1;i<=cnt;i++)
    {
        for(auto it:se[i])
        {
            l=it.l,r=it.r;
            modify(1,l,r,it.id);
        }
    }
   query(1);
    cout<<ans.size()<<endl;
    for(auto it:ans)
    {
        auto res=it.second;
        cout<<res.size()<<" ";
        for(auto x:res)
            cout<<x<<" ";
        cout<<endl;
    }
}


int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    //   cout<<fixed<<setprecision(10);
    mt19937 rng(233);
    int t=1;
      cin>>t;
    while(t--)
    {
        solve();
    }
    ac;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值