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; }