链接
题解
维护一个并查集,每次一个人上线,我就暴力把这个人所在的集合根其相邻且在线的人的集合给合并
一个人下线,就直接把他所在的并查集的
这样不好的一点在于,每次的代价都是 的,如果他总是操作一个度数很大的点,就肯定会
由于所有的点的度数之和等于 ,所以度数大于 的点的个数不会超过 个
度数小的我仍然可以使用暴力做法
但是度数大的,我考虑下能不能优化
注意到我们加入一个点的时候:我要把在图上和这个点相邻的而且在线的人都和自己合并
突破点就在这个"在线"
因为每次操作只能造成一个点从不在线变成在线,这就给人一种势能分析的感觉,有些需要势能分析的题目,我们总是在一些操作中积累势能,在某些操作中释放势能。现在我要处理这些度数大的点,就需要找一种“释放势能”的操作。
显然,我要对每个度数大的点记录所有影响到他的修改。也就是,和他在图上相邻的点,如果被激活了,就会影响到他,我们记录这个影响,说的明白一点,对每个度数大的点记录被激活的邻点。
当这个度数大的点一直在沉睡的时候,我就记录那些上线的临点。在度数大的点突然醒来的那一刻,我就要考虑处理这些信息了。显然我只要挨个进行并查集合并就行了,然后把这些信息丢掉(必须丢掉才能释放势能)。
具体一点:当小点上线时,我就暴力处理。同时看一下他是否影响到了某些沉睡中的大点,如果影响了就记录一下,小点上线的复杂度是 的。大点苏醒时,先处理前面积累的信息(势能分析得到这一部分是 的),这个时候我其实已经处理完了所有“在线小点”,接下来我再看一下这个大点的苏醒给其它沉睡的大点有没有影响,因为大点的个数是 的,所以这部分的复杂度还是 。
上面还只是说了上线,下线怎么处理呢?
根据最初描述的并查集做法,下线就直接把当前的并查集 就行了,其实就相当于留了个空壳在那个并查集的树上。
为了保证答案的正确性,再次回来时我可以创建一个新的节点,再开始合并,但是这样的话我处理大点的时候就不能把处理过的信息丢掉,因为处理过的信息在创建新的节点时都丢失了,必须重新合并,那这样复杂度岂不是又升上去了?
很烦,怎么解决呢。
首先想到,能不能“钻回空壳”?这样显然是不对的,举个例子 这条链,上线的顺序是 ,显然现在并查集中 是一个队伍,然后 下线, 上线,这个时候显然 是自己一个人一支队伍,如果我“钻回空壳”,他就会变成和 同一队伍。
但是我又猜想,是不是说,只有在相邻的朋友都掉线了的之后,“钻回空壳”才是错的?
想了一会感觉这个猜想没什么问题,应该是对的,因为只要有一个朋友在线,我即使创建了新的并查集节点,也是去和这个朋友连边,而旧的空壳本身就是参加了这个朋友所在的队伍,所以感觉信息是正确的。
那么我就再多维护一个东西:在线的朋友数量
具体就不多说了,说到这里应该思路都介绍全了
代码
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#define iinf 0x3f3f3f3f
#define linf (1ll<<60)
#define eps 1e-8
#define maxn 200010
#define maxe 200010
#define maxs 350
#define cl(x) memset(x,0,sizeof(x))
#define rep(i,a,b) for(i=a;i<=b;i++)
#define drep(i,a,b) for(i=a;i>=b;i--)
#define em(x) emplace(x)
#define emb(x) emplace_back(x)
#define emf(x) emplace_front(x)
#define fi first
#define se second
#define de(x) cerr<<#x<<" = "<<x<<endl
using namespace std;
using namespace __gnu_pbds;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
ll read(ll x=0)
{
ll c, f(1);
for(c=getchar();!isdigit(c);c=getchar())if(c=='-')f=-f;
for(;isdigit(c);c=getchar())x=x*10+c-0x30;
return f*x;
}
struct UnionFind
{
ll f[maxn], size[maxn];
void init(ll n)
{
for(auto i=1;i<=n;i++)f[i]=i, size[i]=0;
}
ll find(ll x){return f[x]==x?x:f[x]=find(f[x]);}
void merge(ll x, ll y)
{
auto fx=find(x), fy=find(y);
f[fx]=fy;
if(fx!=fy)size[fy]+=size[fx];
}
}uf;
struct Graph
{
int etot, head[maxn], to[maxe], next[maxe], w[maxe];
void clear(int N)
{
for(int i=1;i<=N;i++)head[i]=0;
etot=0;
}
void adde(int a, int b, int c=0){to[++etot]=b;w[etot]=c;next[etot]=head[a];head[a]=etot;}
#define forp(_,__) for(auto p=__.head[_];p;p=__.next[p])
}G;
vector<ll> online_neighbour[maxn], to_big[maxn];
ll bigtot, deg[maxn], S=316, isbig[maxn], isonline[maxn], roomtot, room[maxn], cnt[maxn];
int main()
{
ll i, u, v, N, M, Q, T=read(), j, kase;
rep(kase,1,T)
{
printf("Case #%lld:\n",kase);
N=read(), M=read(), Q=read();
G.clear(N), bigtot=roomtot=0;
rep(i,1,N)isonline[i]=0, isbig[i]=0, to_big[i].clear(), online_neighbour[i].clear(), deg[i]=0, cnt[i]=0;
rep(i,1,M)
{
u=read(), v=read();
deg[u]++, deg[v]++;
G.adde(u,v), G.adde(v,u);
}
rep(i,1,N)
if(deg[i]>S)
{
isbig[i]=1;
}
rep(i,1,N)
forp(i,G)
if( isbig[ G.to[p] ] )
to_big[i].emb(G.to[p]);
rep(i,1,N)room[i]=i; roomtot=N;
uf.init(Q+N);
while(Q--)
{
char s[3];
scanf("%s",s);
ll guy = read();
if(s[0]=='i')
{
if(isonline[guy])continue;
isonline[guy]=1;
ll x=cnt[guy];
for(auto to:to_big[guy])if(isonline[to])x++;
if(x==0)room[guy] = ++roomtot;
uf.size[ uf.find(room[guy]) ]++;
if(isbig[guy])
{
for(auto x:online_neighbour[guy])
{
if(isonline[x])uf.merge(room[guy],room[x]);
}
online_neighbour[guy].clear();
}
else
forp(guy,G)
{
if(isonline[G.to[p]])
{
uf.merge(room[guy],room[G.to[p]]);
}
cnt[G.to[p]]++;
}
for(auto x:to_big[guy])
{
online_neighbour[x].emb(guy);
cnt[x]++;
}
}
if(s[0]=='o')
{
if(isonline[guy]==0)continue;
isonline[guy]=0;
uf.size[ uf.find(room[guy]) ] --;
if(isbig[guy]==0)forp(guy,G)cnt[G.to[p]]--;
}
if(s[0]=='q')
{
if(isonline[guy])printf("%lld\n",uf.size[ uf.find(room[guy]) ]);
else printf("0\n");
}
}
}
return 0;
}