2020牛客暑期多校训练营(第三场) G

欢迎来玩有第三场的LABCEG 传送门

G

并查集。看到本题第一想法就是并查集,由于题目中合并两个集合的条件是两集合之间的点有边相连,那么我们考虑怎样的点,对增加集合元素有贡献。

首先已经被扫过的点显然对答案没有贡献,我们发现每次扩展出新的点可能对答案会产生贡献,我们不妨称之为可扩展点或者说是边界点。对于一个集合我们可以开一个队列保存有哪些边界点,每一次向外扩展,现当于把队列内的边界点取出往外扩展一层,并且加入的集合。我们不难发现这个过程有点类似于bfs,其实向外扩展一层现当于向外执行一层bfs,因此我们每次往外扩展时采用这种类bfs的策略来找到需要合并哪些集合。但是这题卡空间,我们无法开,那么我们需要手写用链表实习队列。

对于集合合并我们必须保持复杂度,必须同时使用路径压缩和按秩合并。那么我们就面临一个问题:染色。我们知道在按秩合并的过程中我们无法保证某个集合的颜色为那么我们只需要开个数组或者map维护一下每个集合的颜色就行了(一开始所有集合的颜色都是自己)。

关于此题还有一个细节需要注意,对于一个集合如果其已经被一个集合合并,我们应当认为是空的,即当时该步操作我们无需执行任何操作。具体说明可以见样例解释。

#include <bits/stdc++.h>

using namespace std;

#define ll long long
ll input(){
    ll x=0,f=0;char ch=getchar();
    while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return f? -x:x;
}

#define pb push_back

const int N=8e5+1;

struct node{
    int w;
    node *next;
};

struct Qu{
    node *head,*end,*del;

    int front(){
        return head->w;
    }

    void push(int x){
        if(head==NULL&&end==NULL) head=end=new(node);
        else{
            end->next=new(node);
            end=end->next;
        }
        end->w=x;
    }

    void pop(){
        del=head;
        if(head==end) head=end=NULL;
        else head=head->next;
        delete(del);
    }

    bool empty(){
        return head==NULL&&end==NULL;
    }
}q[N];

int fa[N],rk[N];

int find(int x){ return fa[x]==x? x:(fa[x]=find(fa[x]));} 
void merge(int x,int y){
    x=find(x),y=find(y);

    if(x!=y){
        if(rk[x]>=rk[y]){
            while(!q[y].empty()) q[x].push(q[y].front()),q[y].pop();
            fa[y]=x,rk[x]+=rk[y];
        }else{
            while(!q[x].empty()) q[y].push(q[x].front()),q[x].pop();
            fa[x]=y,rk[y]+=rk[x];
        }
    }
}

vector <int> G[N];
Qu tmp;
map <int,int> mp;
int n,m;

void Solve(){
    n=input(),m=input();

    mp.clear();
    for(int i=1;i<=n;i++){
        while(!q[i].empty()) q[i].pop();q[i].push(i);
        rk[i]=1,fa[i]=i;G[i].clear();
        mp[i]=i;
    }


    for(int i=1;i<=m;i++){
        int u=input()+1,v=input()+1;
        G[u].pb(v),G[v].pb(u);
    }

    int QQ=input();
    for(int i=1;i<=QQ;i++){
        while(!tmp.empty()) tmp.pop();
        int u=input()+1,tu;
        tu=u;
        if(u!=mp[find(u)]) continue;
        u=find(u);
        while(!q[u].empty()) tmp.push(q[u].front()),q[u].pop();
        // cout<<i<<":"<<tmp.front()<<endl;
        while(!tmp.empty()){
            int t=tmp.front();tmp.pop();
            for(auto v:G[t]){
                merge(v,t);
            }
        }

        mp[find(u)]=tu;
    }

    for(int i=1;i<=n;i++){
        printf("%d%c",mp[find(i)]-1,i==n? '\n':' ');
    }
}

int main(){
    int T=input();
    while(T--)
        Solve();
}
全部评论

相关推荐

会飞的猿:我看你想进大厂,我给你总结一下学习路线吧,java语言方面常规八股要熟,那些java的集合,重点背hashmap八股吧,jvm类加载机制,运行时分区,垃圾回收算法,垃圾回收器CMS、G1这些,各种乐观锁悲观锁,线程安全,threadlocal这些。在进阶一些的比如jvm参数,内存溢出泄漏排查,jvm调优。我这里说的只是冰山一角,详细八股可以去网上找,这不用去买,都免费资源。mysql、redis可以去看小林coding,我看你简历上写了,你一定要熟,什么底层b+树、索引结构、innodb、mvcc、undo log、redo log、行级锁表级锁,这些东西高频出现,如果面试官问我这些我都能笑出来。消息队列rabbitmq也好kafka也好,学一种就行,什么分区啊副本啊确认机制啊怎么保证不重复消费、怎么保证消息不丢失这些基本的一定要会,进阶一点的比如LEO、高水位线、kafka和rocketmq底层零拷贝的区别等等。计算机网络和操作系统既然你是科班应该理解起来问题不大,去看小林coding这两块吧,深度够了。spring boot的八股好好看看吧,一般字节腾讯不这么问,其他的java大厂挺爱问的,什么循环依赖啥的去网上看看。数据结构的话科班应该问题不大,多去力扣集中突击刷题吧。项目的话其实说白了还是结合八股来,想一想你写的这些技术会给你挖什么坑。除此之外,还有场景题、rpc、设计模式、linux命令、ddd等。不会的就别往简历上写了,虽然技术栈很多的话好看些,但背起来确实累。总结一下,多去实习吧,多跳槽,直到跳到一个不错的中厂做跳板,这是一条可行的进大厂的路线。另外,只想找个小厂的工作的话,没必要全都照这些准备,太累了,重点放在框架的使用和一些基础八股吧。大致路线就这样,没啥太多难度,就是量大,你能达到什么高度取决于你对自己多狠,祝好。
点赞 评论 收藏
分享
02-24 10:34
门头沟学院 Java
在思考的熊熊很讨厌吃香菜:之前发最美的女孩基本爱答不理,发最帅的hr终于有反馈了,女孩子也要自信起来
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务