HDU 3938 Portal

题目大意:

给定一个n个点m条边的无向图

然后有q个询问

问你当限制可以通过的边的最大权值为a时,

有多少组点可以相连(u与u相连不计,u与v和v与u只记一次)

 

解法:

首先我们应该把询问离线处理,将它们按a的大小排序,因为对于较大的a的答案是由较小的a一点点推出来的。

然后我们思考这个限制边最大权值一点点增大的过程,和克鲁斯卡尔算法中求最小生成树时从小到大一点点把边加到里面的过程是类似的。

那么问题就在于如何统计答案;

此时假设我们在做类似于克鲁斯卡尔算法加入一条边从u到v时

假设这两个点已经在之前被间接连在一起了,那么这条边是没有意义的。

如果还未连在一起

设u所能连接点的数量为gs[u],v所能连接点的数量为gs[v]

那么这条边的加入会使ans增加gs[u]*gs[v];

之后我们把这两个点集并入一起

#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1e4+5;
int anc[N],ans[N],gs[N];
struct E{
    int u,v,q,sl;
    bool operator<(const E &a)const{
        return q<a.q;
    }
}e[5*N];
struct X{
    int a,id;
    bool operator<(const X &b)const{
        return a<b.a;
    }
}x[N];
int fi(int a){
    int b=a;
    while(anc[b]!=b) b=anc[b];
    while(anc[a]!=b){
        int t=anc[a];
        anc[a]=b;
        a=t;
    }
    return b;
}
int main(){
    int n,m,q;
    while(scanf("%d%d%d",&n,&m,&q)==3){
        for(int i=1;i<=m;++i) scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].q),e[i].sl=0;
        for(int i=1;i<=q;++i) scanf("%d",&x[i].a),x[i].id=i;
        for(int i=1;i<=n;++i) gs[anc[i]=i]=1;
        sort(e+1,e+m+1);
        sort(x+1,x+q+1);
        for(int i=1;i<=m;++i){
            int fu=fi(e[i].u),fv=fi(e[i].v);
            e[i].sl=e[i-1].sl;
            if(fu!=fv){
                e[i].sl+=gs[fu]*gs[fv];
                gs[fu]+=gs[fv];
                anc[fv]=fu;
            }
        }
        for(int i=1,j=0;i<=q;++i){
            for(;j<m&&e[j+1].q<=x[i].a;++j) ;
            ans[x[i].id]=e[j].sl;
        }
        for(int i=1;i<=q;++i) printf("%d\n",ans[i]);
    }
    return 0;
}