题意:
先给出一个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;
}