• 【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
    ",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
    ",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
    ",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
    ",LCA(x,y));
            }
            return 0;
    }
    
  • 相关阅读:
    如何修改帝国cms文章点击量默认值和成倍增加
    微信读书App来了 小伙伴们快去占榜吧
    (二)第十回 同日生辰情解契语 异姓兄弟冰释嫌隙[林大帅作品集]
    第十回(一) 同日生辰情解契语 异姓兄弟冰释嫌隙
    帝国cms如何调用指定id的文章到首页?
    微信支付又下一城:可以缴税了
    笔记:mysql升序排列asc,降序排列desc
    第九回(二):任侠厨子轻解厄围 夜半蹴鞠为泄忿闷[林大帅作品集]
    第九回(一):任侠厨子轻解厄围 夜半蹴鞠为泄忿闷
    第八回(二)新年晚会艺压群芳 文理分科三人聚首
  • 原文地址:https://www.cnblogs.com/SillyTieT/p/11338964.html
Copyright © 2020-2023  润新知