http://hihocoder.com/contest/icpcbeijing2017/problem/3
带撤销的并查集+莫队
口胡一波复杂度,暂且认为并查集的合并和撤销都是O(1)的
考虑离线询问,莫队处理,对于一个左右都在块内的询问直接暴力求解,求解一次就是sqrt(n)的
然后开始枚举左端点所在的块,首先对右端点排序,块内右端点递增,此时的询问都是这样的,左端点在[a1,a2]右端点在[a2+1,n],我们考虑这样一条边,u,v,如果uv都在[a2+1,n]这样的边不用撤销,因为后面一定会用到,对于u在[a1,a2]的边,这是需要撤销的,因此我们每次维护[a2+1,lastr]lastr是上一次询问的r 下一次的询问r比上一次大,然后再暴力维护左边(要撤销的边)。这样的复杂度这样算,对于一条边,被枚举只有2种情况,此时枚举的左分块包含左断点,这样被枚举的次数就是左分块所在的询问个数,第二种就是枚举的左分块不包含左端点,这样最多被枚举1次,均摊每条边被枚举的次数就是sqrt(m)(口胡);
#include <iostream>
#include <stdio.h>
#include <cstring>
#include <algorithm>
#include <vector>
#include <stack>
#include <map>
#include <cmath>
using namespace std;
typedef long long int lll;
namespace Solve {
lll n,m,q;
lll ans;
const lll maxn = 55009;
lll fa[maxn],si[maxn];
lll lef[maxn*2],rig[maxn*2];
lll anss[maxn*2];
vector<lll> modui[1009];
lll block;
stack< pair<lll *,lll > > stak;
vector<lll> vv1[maxn];
void init(){
ans = 0;
for(lll i=0;i<maxn;i++){
fa[i] = i;
si[i]= 1;
vv1[i].clear();
}
for(lll i=0;i<1009;i++){
modui[i].clear();
}
while (!stak.empty()) {
stak.pop();
}
ans = 0;
block = 0;
}
lll find(lll x){
while (fa[x]!=x) {
x = fa[x];
}
return x;
}
void uni(lll u,lll v,bool on){
u = find(u);
v = find(v);
if(u==v) return ;
if(si[u]<si[v]) swap(u,v);
if(on) stak.push({&fa[v],fa[v]});
fa[v] = u;
if(on) stak.push({&ans,ans});
ans-=si[u]*(si[u]-1)/2;
ans-=si[v]*(si[v]-1)/2;
if(on) stak.push({&si[u],si[u]});
si[u]+=si[v];
ans+=si[u]*(si[u]-1)/2;
}
void back(){
while (!stak.empty()) {
*stak.top().first = stak.top().second;
stak.pop();
}
}
void solve(){
init();
scanf("%lld%lld%lld",&n,&m,&q);
block = sqrt(n);
for(lll i=1;i<=m;i++){
lll u,v;
scanf("%lld%lld",&u,&v);
vv1[u].push_back(v);
vv1[v].push_back(u);
}
for(lll i=1;i<=q;i++){
scanf("%lld%lld",&lef[i],&rig[i]);
lll lb = (lef[i]-1)/block,rb =(rig[i]-1)/block;
if(lb==rb){
lll tmp = lef[i];
while (tmp<=rig[i]) {
for(lll v : vv1[tmp])if(v<=rig[i]&& v>=lef[i]){
uni(tmp, v, true);
}
tmp++;
}
anss[i] = ans;
back();
}else{
modui[lb].push_back(i);
}
}
for(lll i=0;(i+1)*block<=n;i++){
sort(modui[i].begin(), modui[i].end(), [](lll a,lll b){ return rig[a]<rig[b];});
ans = 0;
while (!stak.empty()) {
stak.pop();
}
for(lll i=1;i<=n;i++){
fa[i]= i;
si[i] = 1;
}
lll tl = (i+1)*block;
lll tr = tl-1;
for(lll j :modui[i]){
while (tr<rig[j]) {
tr++;
for(lll v:vv1[tr]) if(v<=tr && v>=tl){
uni(tr, v, false);
}
}
while (tl>lef[j]) {
tl--;
for(lll v:vv1[tl]) if(v<=tr && v>=tl){
uni(tl, v, true);
}
}
anss[j] = ans;
back();
tl = (i+1)*block;
}
}
for(lll i=1;i<=q;i++){
printf("%lld\n",anss[i]);
}
}
}
int main(int argc, const char * argv[]) {
lll T;
scanf("%lld",&T);
while (T--) {
Solve::solve();
}
return 0;
}