【NOIP】2013提高组Day1T3 货车运输

此题写法蛮多的,倍增和启发式合并的写法想必大家已经了解了,本人稍微提一下好了。

而至于按秩合并和整体二分的写法,本人就稍微详细地讲解一下。

解法一:生成树+倍增

用克鲁斯卡尔算法构建出若干棵最大生成树,那么每个询问就转化成了求x到y路径上的最小边权,用倍增维护即可。

代码:

#include<bits/stdc++.h>
#define MAXN 100010
using namespace std;
bool f;
int n,m,q;
struct node {
        int st,ed,v;
} Edge[MAXN];
int pre[MAXN];
bool cmp(node A,node B) {
        return A.v>B.v;
}
int Find(int x){
        if(pre[x]==x)return x;
        return pre[x]=Find(pre[x]);
}
struct ______________{
        vector<node>G[MAXN];
        int F[MAXN][20],num[MAXN][20],lg[MAXN],deep[MAXN],root[MAXN];
        bool mark[MAXN],vis[MAXN];
        void DFS(int x,int fa,int val,int rt){
                root[x]=rt;
                F[x][0]=fa;
                num[x][0]=val;
                deep[x]=deep[fa]+1;
                for(int i=1;(1<<i)<=deep[x];i++)F[x][i]=F[F[x][i-1]][i-1],num[x][i]=min(num[x][i-1],num[F[x][i-1]][i-1]);
                for(int i=0;i<G[x].size();i++){
                        int t=G[x][i].ed,val=G[x][i].v;
                        if(t==fa)continue;
                        DFS(t,x,val,rt);
                }
        }
        int LCA(int x,int y){
                if(deep[x]<deep[y])swap(x,y);
                int res=2e9+7;
                while(deep[x]>deep[y]){
                        res=min(res,num[x][lg[deep[x]-deep[y]]-1]);
                        x=F[x][lg[deep[x]-deep[y]]-1];
                }
                if(x==y)return res;
                for(int i=lg[deep[x]]-1;i>=0;i--){
                        if(F[x][i]==F[y][i])continue;
                        res=min(res,num[x][i]);
                        res=min(res,num[y][i]);
                        x=F[x][i],y=F[y][i];
                }
                res=min(res,num[x][0]);
                res=min(res,num[y][0]);
                return res;
        }
        void work(){
                for(int i=1;i<=MAXN-10;i++)lg[i]=lg[i-1]+((1<<lg[i-1])==i);
                for(int i=1;i<=n;i++)pre[i]=i;
                for(int i=1; i<=m; i++) {
                        int x,y,z;
                        scanf("%d %d %d",&x,&y,&z);
                        Edge[i]=node {x,y,z};
                }
                sort(Edge+1,Edge+1+m,cmp);
                for(int i=1;i<=m;i++){
                        int fx=Find(Edge[i].st),fy=Find(Edge[i].ed);
                        if(fx==fy)continue;
                        mark[Edge[i].st]=mark[Edge[i].ed]=true;
                        pre[fx]=fy;
                        G[Edge[i].st].push_back(node{0,Edge[i].ed,Edge[i].v});
                        G[Edge[i].ed].push_back(node{0,Edge[i].st,Edge[i].v});
                }
                for(int i=1;i<=n;i++){
                        if(!root[i]){
                                DFS(i,0,0,i);
                        }
                }
                scanf("%d",&q);
                for(int i=1;i<=q;i++){
                        int x,y;
                        scanf("%d %d",&x,&y);
                        if(root[x]!=root[y]){
                                puts("-1");
                                continue;
                        }
                        printf("%d\n",LCA(x,y));
                }
        }
}p100;
bool ff;
int main() {
        scanf("%d %d",&n,&m);
        p100.work();
        return 0;
}

解法二:并查集+启发式合并

先把询问挂在点上,并查集合并时不路径压缩,把询问数量小的点向大的点合并,同时更新询问答案。

代码:

#include<bits/stdc++.h>#define MAXN 100010using namespace std;
int pre[MAXN],q,n,m,ans[MAXN];
struct node{
        int st,ed,v;
}Edge[MAXN];
struct Query{
        int ed,id;
};
vector<Query> Q[MAXN];
bool cmpEdge(node A,node B){
        return A.v>B.v;
}
int Find(int x){
        while(1){
                if(pre[x]==x)return x;
                x=pre[x];
        }
}
void merge(int x,int y,int val){
        int fx=Find(x),fy=Find(y);
        if(fx==fy)return;
        if(Q[fx].size()>Q[fy].size())swap(fx,fy);
        pre[fx]=fy;
        for(int i=0;i<Q[fx].size();i++){
                int t=Q[fx][i].ed,id=Q[fx][i].id;
                if(ans[id]!=-1)continue;
                int ft=Find(t);
                if(ft==fy)ans[id]=val;
        }
        for(int i=0;i<Q[fx].size();i++)Q[fy].push_back(Q[fx][i]);
}
int main() {
        memset(ans,-1,sizeof(ans));
        scanf("%d %d",&n,&m);
        for(int i=1;i<=m;i++){
                int st,ed,v;
                scanf("%d %d %d",&st,&ed,&v);
                Edge[i].st=st,Edge[i].ed=ed,Edge[i].v=v;
        }
        sort(Edge+1,Edge+1+m,cmpEdge);
        scanf("%d",&q);
        for(int i=1;i<=q;i++){
                int x,y;
                scanf("%d %d",&x,&y);
                Q[x].push_back((Query){y,i});
                Q[y].push_back((Query){x,i});
        }
        for(int i=1;i<=n;i++)pre[i]=i;
        for(int i=1;i<=m;i++)merge(Edge[i].st,Edge[i].ed,Edge[i].v);
        for(int i=1;i<=q;i++)printf("%d\n",ans[i]);
        return 0;
}

解法三:整体二分+并查集

假设只有一个询问,那么我们肯定是二分载重量,然后连接大于等于载重量的边,判断x,y是否连通。

那么对于很多询问呢?

考虑到如果这些询问的mid值是具有单调性的,那么当前连边情况就可以由上一个直接转移过来(不需要重新加边)。
因此,我们可以先将mid值从大到小排序,然后再查过来,同时不断加边。

代码:

#include<bits/stdc++.h>#define MAXN 100010using namespace std;
int pre[MAXN],q,n,m,Ans[MAXN];
struct node{
        int st,ed,v;
}Edge[MAXN];
bool cmpEdge(node A,node B){
        return A.v>B.v;
}
struct Query{
        int l,r,mid,ans,id,st,ed;
}Q[MAXN];
bool cmpQuery(Query A,Query B){
        return A.mid>B.mid;
}
int Find(int x){
        if(pre[x]==x)return x;
        return pre[x]=Find(pre[x]);
}
void merge(int x,int y){
        int fx=Find(x),fy=Find(y);
        if(fx==fy)return;
        pre[fx]=fy;
}
bool check(int x,int y){
        int fx=Find(x),fy=Find(y);
        return (fx==fy);
}
int main() {
        scanf("%d %d",&n,&m);
        for(int i=1;i<=m;i++){
                int st,ed,v;
                scanf("%d %d %d",&st,&ed,&v);
                Edge[i].st=st,Edge[i].ed=ed,Edge[i].v=v;
        }
        sort(Edge+1,Edge+1+m,cmpEdge);
        scanf("%d",&q);
        for(int i=1;i<=q;i++){
                int x,y;
                scanf("%d %d",&x,&y);
                Q[i]=(Query){1,100000,50000,-1,i,x,y};
        }
        int cnt=20;
        while(cnt--){
                for(int i=1;i<=n;i++)pre[i]=i;
                for(int i=1;i<=q;i++)Q[i].mid=(Q[i].l+Q[i].r)>>1;
                sort(Q+1,Q+1+q,cmpQuery);
                for(int i=1,j=1;i<=q;i++){
                        if(Q[i].l>Q[i].r)continue;
                        while(Edge[j].v>=Q[i].mid&&j<=m){
                                merge(Edge[j].st,Edge[j].ed);
                                j++;
                        }
                        if(check(Q[i].st,Q[i].ed)){
                                Q[i].ans=Q[i].mid;
                                Q[i].l=Q[i].mid+1;
                        }
                        else Q[i].r=Q[i].mid-1;
                }
        }
        for(int i=1;i<=q;i++)Ans[Q[i].id]=Q[i].ans;
        for(int i=1;i<=q;i++)printf("%d\n",Ans[i]);
        return 0;
}

解法四:并查集+按秩合并

跟第一种写法差不多,只不过构建最大生成树时我们不路径压缩,用按秩合并的方法,把深度小的点合并到大的点上面。

注意了,此时连边并不是把两个点直接连边,而是把它们的祖先连边(边权还是一样)。

可以证明:因为我们是按边从大到小加的,而要查询路径上的边权最小值。此时,这条边把两个集合合并在了一起,那么一个集合到另一个集合必定要经过这条边,而这条边又是目前最小的(其它边可以不管了),所以我们只用确保路径经过了这条边即可。

按照这样构成的生成树深度最多只有log(n),因此找路径暴力遍历即可。

代码:


#include<bits/stdc++.h>
#define MAXN 100010
using namespace std;
int n,m,q;
struct node {
        int st,ed,v;
} Edge[MAXN];
int pre[MAXN],dis[MAXN],deep[MAXN],W[MAXN],root[MAXN];
vector<node> G[MAXN];
bool cmp(node A,node B) {
        return A.v>B.v;
}
int Find(int x) {
        while(1){
                if(pre[x]==x)return x;
                x=pre[x];
        }
}
void merge(int x,int y,int v){
        int fx=Find(x),fy=Find(y);
        if(fx==fy)return;
        if(dis[fx]>dis[fy])swap(fx,fy);
        pre[fx]=fy;
        if(dis[fx]==dis[fy])dis[fy]++;
        G[fx].push_back((node){0,fy,v});
        G[fy].push_back((node){0,fx,v});
}
void DFS(int x,int fa,int val,int k){
        root[x]=k;
        deep[x]=deep[fa]+1;
        pre[x]=fa;
        W[x]=val;
        for(int i=0;i<G[x].size();i++){
                int t=G[x][i].ed;
                if(t==fa)continue;
                DFS(t,x,G[x][i].v,k);
        }
}
int LCA(int x,int y){
        if(deep[x]<deep[y])swap(x,y);
        int res=2e9+7;
        while(deep[x]>deep[y])res=min(res,W[x]),x=pre[x];
        while(x!=y){
                res=min(res,W[x]),res=min(res,W[y]);
                x=pre[x],y=pre[y];
        }
        return res;
}
int main() {
        scanf("%d %d",&n,&m);
        for(int i=1; i<=n; i++)pre[i]=i;
        for(int i=1; i<=m; i++) {
                int x,y,z;
                scanf("%d %d %d",&x,&y,&z);
                Edge[i]=(node) {x,y,z};
        }
        sort(Edge+1,Edge+1+m,cmp);
        for(int i=1;i<=m;i++){
                merge(Edge[i].st,Edge[i].ed,Edge[i].v);
        }
        for(int i=1;i<=n;i++){
                if(!root[i])DFS(i,0,0,i);
        }
        scanf("%d",&q);
        for(int i=1; i<=q; i++) {
                int x,y;
                scanf("%d %d",&x,&y);
                if(root[x]!=root[y]) {
                        puts("-1");
                        continue;
                }
                printf("%d\n",LCA(x,y));
        }
        return 0;
}
posted @ 2019-08-12 12:17  TieT  阅读(168)  评论(0编辑  收藏  举报