2020牛客暑期多校训练营(第八场)A All-Star Game —— 线段树分治+并查集,关于unordered_map自定义pair_table

96 篇文章 1 订阅
24 篇文章 0 订阅

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<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值