ICPC Central Europe Regional Contest 2019 Saba1000kg(复杂度均摊-并查集)

 

题目大意:

n个点,m条边,q个询问

每次给出k个能用的点,把不能用的点的连接的边删除后问有几个联通块

(k[i]的和小于n)

 

题目思路:

好妙啊~~~,还有点卡常

首先考虑暴力做法

一号暴力选手

对于每次询问,如果我们对给出的点两两合并

假设一次给出n个点,复杂度最坏为O(n*n*q) [这时候q=1]

二号暴力选手

对于每次询问,暴力跑一边所有的边,合并边的两个端点

复杂度最坏O(m*q)

 

看到上面两种暴力方法其实都是可行的

要想一个方法降低复杂度,我们综合上面两种方法

题目说k[i]的和小于n

 

所以

我们对于大于sqrt(n)的k[i]采取二号暴力选手的做法

对于小于等于sqrt(n)的k[i]采取一号暴力选手的做法

 

为什么可以呢?

如果此时给出k(k<=sqrt(n))个点

一号选手的复杂度最坏为(sqrt(n)*sqrt(n)*q)

但是这种询问我们最多进行sqrt(n)次

所以复杂度最坏为O(n*sqrt(n))

 

如果此时给出k(k>sqrt(n))个点

我们暴力跑所有的边,这种询问最多也是进行sqrt(n)次

复杂度最坏为O(sqrt(n)*m)

 

 

code:

int head[maxn],cnt;
struct node {
    int u,v,w,next;
} e[maxn];
void add(int u,int v,int w) {
    e[cnt].u=u,e[cnt].v=v,e[cnt].w=w;
    e[cnt].next=head[u],head[u]=cnt++;
}
int n,m,qq,p[maxn],a[maxn],vis[maxn];
vector<int>s[maxn];
int find(int x) {
    if(p[x]==x) return x;
    return p[x] = find(p[x]);
}
void combine(int x,int y) {
    int dx = find(x);
    int dy = find(y);
    if(dx==dy) return ;
    p[dx] = dy;
}
int main() {
    mst(head,-1);
    n=read(),m=read(),qq=read();
    rep(i,1,n) p[i] = i;
    for(int i=1 ; i<=m ; i++) {
        int u,v,w;
        u=read(),v=read();
        add(u,v,1),add(v,u,1);
        s[u].push_back(v);
        s[v].push_back(u);
    }
    rep(i,1,n) sort(s[i].begin(),s[i].end());

    while(qq--) {
        int temp=0;
        int k = read();
        int t = sqrt(n) ;
        for(int i=1 ; i<=k ; i++) {
            a[i] = read();
            vis[a[i]]=1;
            p[a[i]] = a[i];
        }
        if(k<t) {
            for(int i=1 ; i<=k ; i++ ) {
                for(int j=1 ; j<=k ; j++) {
                    auto it = lower_bound(s[a[i]].begin(),s[a[i]].end(),a[j]);
                    if(it==s[a[i]].end()||*it!=a[j]) continue;
                    combine(a[i],a[j]);
                }
            }
        } else {
            for(int i= 0; i<cnt; i++) {
                int u = e[i].u;
                int v = e[i].v;
                if(!vis[u]||!vis[v]) continue;
                combine(u,v);
            }
        }
        for(int i=1 ; i<=k ; i++) if(find(a[i])==a[i]) temp++;
        for(int i=1 ; i<=k ; i++) vis[a[i]] = 0;
        out(temp);
        puts("");
    }
    return 0;
}
View Code

 

posted @ 2021-04-04 22:17  UpMing  阅读(93)  评论(0编辑  收藏  举报