前置知识点如下:
1. 并查集哈希
2. 可撤销并查集
3. 线段树分治
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0),cin.tie(0)
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
const int N = 2e5 + 10;
int k, n, m;
unordered_map<ll, set<pair<int, int>>>mp; // 存储每条边出现的区间
struct edge{ int u, v, op; }a[N];
vector<int>G[N];
int in[N], out[N], pos[N], tim;
void dfs(int x){
in[x] = ++tim;
pos[tim] = x;
for (auto y : G[x])dfs(y);
out[x] = tim;
}
vector<pair<int, int>>tr[N << 2];
#define ls (p<<1)
#define rs (p<<1|1)
void update(int p, int pl, int pr, int l, int r, int u, int v){
if (l <= pl && pr <= r){
tr[p].emplace_back(u, v);
return;
}
int mid = pl + pr >> 1;
if (l <= mid)update(ls, pl, mid, l, r, u, v);
if (r > mid)update(rs, mid + 1, pr, l, r, u, v);
}
mt19937_64 rnd(time(0));
int fa[N], sz[N];
ull val[N], sum;
stack<pair<int, int>>st;
void init(int n){
for (int i = 1;i <= n;++i)
fa[i] = i, sz[i] = 1, val[i] = rnd();
sum = 0;
}
int find(int x){
return x == fa[x] ? x : find(fa[x]);
}
bool merge(int x, int y){
x = find(x), y = find(y);
if (x == y)return 0;
if (sz[x] > sz[y])swap(x, y);
st.push({ x,y });
sum -= val[x] + val[y];
fa[x] = y, sz[y] += sz[x], val[y] ^= val[x];
sum += val[y];
return 1;
}
unordered_map<ull, vector<int>>ans;
void query(int p, int pl, int pr){
int num = 0;
for (auto [u, v] : tr[p])
num += merge(u, v);
if (pl == pr)ans[sum].push_back(pos[pl]);
else{
int mid = pl + pr >> 1;
query(ls, pl, mid);
query(rs, mid + 1, pr);
}
while (num--){
auto [x, y] = st.top();st.pop();
sum -= val[y];
fa[x] = x, sz[y] -= sz[x], val[y] ^= val[x];
sum += val[x] + val[y];
}
}
ll base = N * 100;
void solve(){
cin >> k >> n >> m;
mp.clear();
for (int i = 1;i <= m;++i){
int u, v;
cin >> u >> v;
mp[u * base + v].insert({ 1,k });
}
for (int i = 1;i <= k;++i)G[i].clear();
for (int i = 2;i <= k;++i){
string s;
int fa, u, v;
cin >> fa >> s >> u >> v;
G[fa].push_back(i);
a[i] = { u,v,s == "add" };
}
tim = 0; dfs(1); // dfs序
for (int i = 2;i <= k;++i){ // 维护边出现的区间集合
ll now = a[i].u * base + a[i].v;
auto& s = mp[now];
if (a[i].op)s.insert({ in[i],out[i] });
else{
// 要删除的区间一定是set内某个区间的一部分
auto it = --s.upper_bound({ out[i] + 1,0 });
auto [l, r] = *it;
s.erase(it);
if (l < in[i])s.insert({ l,in[i] - 1 });
if (r > out[i])s.insert({ out[i] + 1,r });
}
}
for (int i = 1;i <= 4 * k;++i)tr[i].clear(); // 清空线段树
for (auto [val, s] : mp){
int u = val / base, v = val % base;
for (auto [l, r] : s)
update(1, 1, k, l, r, u, v);
}
ans.clear();
init(n); // 初始化并查集
query(1, 1, k); // 线段树分治
cout << ans.size() << endl;
for (auto [sum, res] : ans){
cout << res.size() << ' ';
for (auto x : res)cout << x << ' ';
cout << endl;
}
}
signed main(){
#ifdef FSLSE
freopen("in.in", "r", stdin);
freopen("out.out", "w", stdout);
#endif
IOS;
int T;
cin >> T;
while (T--){
solve();
}
return 0;
}