This way
现在有n个球员,m个球迷,定义第i个球迷喜欢第j个球员当他满足以下条件之一:
1.第i个球迷和第j个球员有连边
2.第i个球迷和第k个球员有连边,且第L个球迷和第k个球员有连边且第L个球迷和第j个球员有连边
现在告诉你一开始球员球迷连边情况,并且接下来有q个询问,每次告诉你球迷x和球员y,如果他们没有连边,就连边,如果有连边就断掉。
每次操作结束后,问你最少需要几个球员才能连上所有的球迷。
赛上没时间看这道题目了,赛后发现这道线段树+并查集的经典题目,上一次这种想法是CF的1140F,那一篇我也写了博客,解法和这次的大同小异,有兴趣可以去看看:This way
首先考虑每条边存在的范围,用线段树+vector维护,然后dfs这棵线段树,将当前有的边加到并查集中,到叶子节点记录答案,然后回溯的时候消去这条边。以上是大致想法,接下来是细节实现:
首先确定并查集存的是什么,我思考了一会之后,发现只有两个及以上不同集合的球迷放到一起了之后,需要的球员数量才会减小,如果单单是加入一个没有球迷的球员,答案不会减小。因此我并查集存的是这个并查集中球迷的数量。(前n个数代表球员,n+1~n+m代表球迷)
num表示现在最少需要的球员数量
所以merge的时候,需要判断一下两个并查集是否都有球迷存在,入如果有的话,就表示需要的球员数量-1.删除的时候亦需判断。
由于有删除这个操作的存在,我们不能路径压缩,需要用启发式合并。
还有一个问题就是如果有球迷没有连边,那么答案就是-1,我用link数组表示每个球迷是否有连边。
那么总体思想和细节都在这里了,之后出现了一个让我十分困扰的问题:TLE
我是一个喜欢用unordered_map的人,但是它不能直接存pair,需要自己定义一下pair的hash_table,此时我一开始定义是这样定义的:
一直T,后来我将dfs和update都去掉了,4000ms,换了map,1000ms…
然后我用map怒冲一发,于是就过了
当时那个气,搞了两页的TLE,原来是被unordered_map埋伏了一手!本来想想算了反正过了,但是最后还是决定解决这个疑惑,毕竟之后再出现这个问题的话,可能会有些麻烦。
冷静思考大胆猜想之后,发现我定义的时候返回值是异或,我猜想是不是他用什么类似哈希链表的方法进行处理,重复率太高导致算法退化,按照我的假设,只需要将重复率降低,就可以提高它的时间复杂度!
于是我换成了这个,怒冲一发,然后不出所料跑得更快了
我感觉算法之完美甚至都不需要快读,大胆假设合理验证:
perfect,于是结合上述假设,我提出让代码更短的猜想:去掉unordered_map,改成map
由此可见牛客的测评姬非常抖啊,那么此次实验到此结束,下面的代码是最后一次的代码。
好像还有LCT的做法,也是维护连通性,但是好像有点麻烦,等我有空了在写把,现在这里口胡一下:
首先这是一张二分图,二分图中是会有环存在的,所以加边的时候,需要看是否在一个联通块内,如果是的话,需要把接下来最早被删去的那条边给cut掉,然后用多个计数器计数总的连通块个数,球员连通块个数,球迷连通块个数。。。之类的吧
#include
using namespace std;
#define pii pair
const int N=4e5+5;
int fa[N],siz[N],link[N],zero,num;
int finds(int x){return fa[x]==x?x:finds(fa[x]);}
vector<pii>vec[N*4];
void update(int l,int r,int root,int ql,int qr,pii v){
if(l>=ql&&r<=qr){
vec[root].push_back(v);
return ;
}
int mid=l+r>>1;
if(mid>=ql)
update(l,mid,root<<1,ql,qr,v);
if(mid<qr)
update(mid+1,r,root<<1|1,ql,qr,v);
}
int ans[N];
void merge(int x,int y,stack<pii>& st){
int fax=finds(x),fay=finds(y);
if(fax==fay)return ;
if(siz[fax]<siz[fay])swap(fax,fay);
siz[fax]+=siz[fay];
if(siz[fax]>=2&&siz[fay])
num--;
fa[fay]=fax;
st.push({fax,fay});
}
void del(stack<pii>& st){
while(!st.empty()){
int fax=st.top().first,fay=st.top().second;
st.pop();
if(siz[fax]<siz[fay])swap(fax,fay);
if(siz[fax]>=2&&siz[fay])
num++;
siz[fax]-=siz[fay];
fa[fay]=fay;
}
}
void dfs(int l,int r,int root){
stack<pii>st;
for(pii i:vec[root]){
link[max(i.first,i.second)]++;
if(link[max(i.first,i.second)]==1)zero--;
merge(i.first,i.second,st);
}
if(l==r){
if(zero)
ans[l]=-1;
else
ans[l]=num;
for(pii i:vec[root]){
link[max(i.first,i.second)]--;
if(!link[max(i.first,i.second)])zero++;
}
del(st);
return ;
}
int mid=l+r>>1;
dfs(l,mid,root<<1);
dfs(mid+1,r,root<<1|1);
for(pii i:vec[root]){
link[max(i.first,i.second)]--;
if(!link[max(i.first,i.second)])zero++;
}
del(st);
}
map<pii,int>mp;
int main()
{
int n,m,q;
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=n+m;i++)fa[i]=i;
for(int i=n+1;i<=n+m;i++)siz[i]=1;
zero=num=m;
for(int i=1;i<=n;i++){
int k,x;
scanf("%d",&k);
while(k--)
scanf("%d",&x),mp[{x+n,i}]=1;
}
for(int i=1;i<=q;i++){
int x,y;
scanf("%d%d",&x,&y);
x+=n;
if(!mp.count({x,y}))
mp[{x,y}]=i;
else{
if(mp[{x,y}]<i)
update(1,q,1,mp[{x,y}],i-1,{x,y});
mp.erase({x,y});
}
}
for(auto i:mp)
update(1,q,1,i.second,q,{i.first.first,i.first.second});
dfs(1,q,1);
for(int i=1;i<=q;i++)
printf("%d\n",ans[i]);
return 0;
}