zoukankan      html  css  js  c++  java
  • 《算法竞赛进阶指南》图论习题

    前言

        算法竞赛进阶指南图论习题。慢慢刷。

    Sightseeing

            这个题求最短路以及比最短路大1的路的条数。关键是次短路如何构成。分析可以发现一个点的次短路一定为相邻点次短路或者最短路构成。所以dijkstra维护最短路和次短路即可。

    #include<cstdio>
    #include<cstring>
    #include<queue>
    using namespace std;
    const int INF=0x3f3f3f3f;
    const int N=1e3+10;
    const int M=1e4+10;
    int head[N],ver[M],edge[M],nex[M],tot=1;
    inline void add(int x,int y,int z) {
        ver[++tot]=y,edge[tot]=z,nex[tot]=head[x],head[x]=tot;
    }
    struct Node {
        bool operator<(const Node &t)const {return d>t.d;}
        Node(int x,int d):x(x),d(d){}
        int x,d;
    };
    //dis1最短路dis2次短路cnt1最短路数量cnt2次短路数量
    int dis1[N],dis2[N],cnt1[N],cnt2[N];
    int dijkstra(int s,int t){
        dis1[s]=0;
        cnt1[s]=1;
        priority_queue<Node> q;
        q.push(Node(s,0));
        while(!q.empty()){
            int x=q.top().x,d=q.top().d;q.pop();
            if(d>dis2[x])continue;
            for(int i=head[x];i;i=nex[i]){
                int y=ver[i],len=d+edge[i];
                if(dis1[y]>len){
                    dis2[y]=dis1[y];cnt2[y]=cnt1[y];
                    dis1[y]=len;cnt1[y]=cnt1[x];
                    q.push(Node(y,len));
                }else if(dis1[y]==len)
                    cnt1[y]+=cnt1[x];
                else if(dis2[y]>len){
                    dis2[y]=len;
                    cnt2[y]=dis1[x]==d?cnt1[x]:cnt2[x];
                    q.push(Node(y,len));
                }else if(dis2[y]==len)
                    cnt2[y]+=dis1[x]==d?cnt1[x]:cnt2[x];
            }
        }
        return cnt1[t]+(dis1[t]+1==dis2[t]?cnt2[t]:0);
    }
    void init(int n){
        for(int i=1;i<=n;++i){
            head[i]=0;
            dis1[i]=dis2[i]=INF;
            cnt1[i]=cnt2[i]=0;
        }
        tot=1;
    }
    int main(){
        int T,n,m,s,t,x,y,w;
        scanf("%d",&T);
        while(T--){
            scanf("%d%d",&n,&m);
            init(n);
            for(int i=0;i<m;++i){
                scanf("%d%d%d",&x,&y,&w);
                add(x,y,w);
            }
            scanf("%d%d",&s,&t);
            printf("%d
    ",dijkstra(s,t));
        }
        return 0;
    }
    View Code

    升降梯上

            把电梯的层数和控制槽的位置作为状态,把各状态提取出再进行连边,跑最短路。

    #include<bits/stdc++.h>
    using namespace std;
    const int INF=0x3f3f3f3f;
    const int N=1000*20+10;
    const int M=N*20+10;
    int head[N],ver[M],edge[M],nex[M],tot=1;
    inline void add(int x,int y,int z) {
        ver[++tot]=y,edge[tot]=z,nex[tot]=head[x],head[x]=tot;
    }
    struct Node {
        bool operator<(const Node &t)const{return d>t.d;}
        Node(int x,int d):x(x),d(d) {}
        int x,d;
    };
    int dis[N],vis[N];
    void dijkstra(int s) {
        memset(dis,0x3f,sizeof(dis));
        memset(vis,0,sizeof(vis));
        dis[s]=0;
        priority_queue<Node> pq;
        pq.push(Node(s,0));
        while(!pq.empty()) {
            int x=pq.top().x;
            pq.pop();
            if(vis[x])continue;
            vis[x]=1;
            for(int i=head[x]; i; i=nex[i]) {
                int y=ver[i];
                if(dis[y]>dis[x]+edge[i]) {
                    dis[y]=dis[x]+edge[i];
                    pq.push(Node(y,dis[y]));
                }
            }
        }
    }
    int n,m,c[30];
    inline int encode(int x,int y){return (x-1)*m+y+1;}
    void build(){
        for(int i=1;i<=n;++i)
            for(int j=0;j<m;++j)
                for(int k=0;k<m;++k)
                    if(i+c[k]>=1&&i+c[k]<=n)
                        add(encode(i,j),encode(i+c[k],k),abs(c[k]*2)+abs(j-k));
    }
    int main(){
        scanf("%d%d",&n,&m);
        int s,ans=INF;
        for(int i=0;i<m;++i){scanf("%d",c+i);if(c[i]==0)s=i;}
        build();
        dijkstra(encode(1,s));
        for(int i=0;i<m;++i)
            ans=min(ans,dis[encode(n,i)]);
        printf("%d",ans==INF?-1:ans);
        return 0;
    }
    View Code

    GF和猫咪的玩具

            简单题,floyd

    #include<bits/stdc++.h>
    using namespace std;
    const int INF=0x3f3f3f3f;
    int mp[110][110];
    int main(){
        int n,m,x,y;
        memset(mp,INF,sizeof(mp));
        scanf("%d%d",&n,&m);
        for(int i=0;i<m;++i){
            scanf("%d%d",&x,&y);
            mp[x][y]=1;
            mp[y][x]=1;
        }
        for(int k=1;k<=n;++k)
            for(int i=1;i<=n;++i)
                for(int j=1;j<=n;++j)
                    mp[i][j]=min(mp[i][j],mp[i][k]+mp[k][j]);
        int ans=0;
        for(int i=1;i<=n;++i)
            for(int j=1;j<=n;++j)
                if(mp[i][j]!=INF)ans=max(ans,mp[i][j]);
        printf("%d",ans);
        return 0;
    }
    View Code

    社交网络

            用floyd计算一下两点间最短路条数以及过一个点的最短路条数。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    int mp[105][105];
    ll cnt[105][105];
    int main(){
        int n,m,x,y,c;
        scanf("%d%d",&n,&m);
        memset(mp,0x3f,sizeof(mp));
        for(int i=0;i<m;++i){
            scanf("%d%d%d",&x,&y,&c);
            mp[x][y]=mp[y][x]=min(mp[x][y],c);
            cnt[x][y]=cnt[y][x]=1;
        }
        for(int k=1;k<=n;++k)
            for(int i=1;i<=n;++i)
                for(int j=1;j<=n;++j)
                    if(mp[i][j]>mp[i][k]+mp[k][j]){
                        mp[i][j]=mp[i][k]+mp[k][j];
                        cnt[i][j]=cnt[i][k]*cnt[k][j];
                    }
                    else if(mp[i][j]==mp[i][k]+mp[k][j])
                        cnt[i][j]+=cnt[i][k]*cnt[k][j];
        double ans;
        for(int k=1;k<=n;++k){
            ans=0;
            for(int i=1;i<=n;++i)
                for(int j=1;j<=n;++j)
                    if(i!=k&&j!=k&&i!=j)
                        ans+=(double)(mp[i][k]+mp[k][j]==mp[i][j]?cnt[i][k]*cnt[k][j]:0)/cnt[i][j];
            printf("%.3f
    ",ans);
        }
        return 0;
    }
    View Code

    Arctic Network

            kruskal求最小生成树,当联通块的数量等于卫星数量时结束,答案即最后加入的边长度。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=600;
    struct Edge{
        Edge(int x,int y,int len2):x(x),y(y),len2(len2){}
        bool operator<(const Edge &t){return len2<t.len2;}
        int x,y,len2;
    };
    int fa[N],x[N],y[N];
    int get(int x){return x==fa[x]?x:fa[x]=get(fa[x]);}
    int dis2(int i,int j){return (x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]);}
    int main(){
        int t,s,p;
        scanf("%d",&t);
        while(t--){
            scanf("%d%d",&s,&p);
            for(int i=1;i<=p;++i){
                fa[i]=i;
                scanf("%d%d",x+i,y+i);
            }
            vector<Edge> e;
            for(int i=1;i<=p;++i)
                for(int j=1;j<=p;++j)
                    if(i!=j)e.push_back(Edge(i,j,dis2(i,j)));
            sort(e.begin(),e.end());
            double ans;
            for(Edge &t:e){
                int u=get(t.x),v=get(t.y);
                if(u==v)continue;
                fa[u]=v;
                if(--p==s){
                    ans=sqrt(t.len2);
                    break;
                }
            }
            printf("%.2f
    ",ans);
        }
        return 0;
    }
    View Code

    [SDOI2013]直径

            自己想的智障方法:首先求了一下直径的条数,设为n,然后对每一条边计算经过它的直径条数是否为n。

            查了题解的方法:

    • 一个点的最长路的另一端一定为直径的端点
    • 任意两条直径必定相交

            那么答案一定为直径上的一段,随便求一条直径,从两边往中间缩就行了。

    自己的方法的代码

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N=5e5+10;
    struct Edge{
        Edge(int to,ll len):to(to),len(len){}
        int to;
        ll len;
    };
    vector<Edge> G[N];
    ll L[N],cnt[N],d,ans,cntd;
    typedef pair<ll,ll> P;
    void dfs(int x,int fa){
        L[x]=0;cnt[x]=1;
        vector<P> v;
        for(Edge &t:G[x]){
            int y=t.to;ll len=t.len;
            if(y==fa)continue;
            dfs(y,x);
            ll d=L[y]+len;
            v.push_back(P(d,cnt[y]));
            if(L[x]>d)continue;
            if(L[x]==d)cnt[x]+=cnt[y];
            else{
                L[x]=d;cnt[x]=cnt[y];
            }
        }
        if(v.size()==0)return;
        sort(v.begin(),v.end(),greater<P>());
        if(v.size()==1){
            if(v[0].first>d){
                d=v[0].first;
                cntd=v[0].second;
            }
            else if(v[0].first==d)
                cntd+=v[0].second;
        }
        else{
            if(v[0].first!=v[1].first){
                ll sum=v[1].second;
                for(int i=2;i<v.size();++i){
                    if(v[i].first!=v[i-1].first)break;
                    sum+=v[i].second;
                }
                if(v[0].first+v[1].first>d){
                    d=v[0].first+v[1].first;
                    cntd=v[0].second*sum;
                }else if(v[0].first+v[1].first==d)
                    cntd+=v[0].second*sum;
            }
            else if(v[0].first+v[1].first>=d){
                int i=0;ll sum=v[0].second;
                while(i+1<v.size()&&v[i+1].first==v[0].first)
                    sum+=v[++i].second;
                ll s=(sum*(sum-1))/2;
                while(i>=0){
                    s-=(v[i].second*(v[i].second-1))/2;
                    --i;
                }
                if(v[0].first+v[1].first>d){
                    d=v[0].first+v[1].first;
                    cntd=s;
                }
                else cntd+=s;
            }
        }
    }
    void dfs2(int x,int fa,ll dd,ll c){
        map<ll,ll> mp;
        mp[dd]=c;
        for(Edge &t:G[x]){
            if(t.to==fa)continue;
            mp[t.len+L[t.to]]+=cnt[t.to];
        }
        for(Edge &t:G[x]){
            if(t.to==fa)continue;
            int y=t.to;
            ll C;
            ll tmp=d-t.len-L[y];
            if(tmp==0)C=cnt[y];
            else if(tmp==t.len+L[y])C=(mp[tmp]-cnt[y])*cnt[y];
            else C=mp[tmp]*cnt[y];
            if(C==cntd)++ans;
        }
        mp.clear();
        mp[dd]=c;
        for(Edge &t:G[x]){
            if(t.to==fa)continue;
            mp[t.len+L[t.to]]+=cnt[t.to];
        }
        for(Edge &t:G[x]){
            if(t.to==fa)continue;
            int y=t.to;
            ll tmp=t.len+L[y];
            auto it=prev(mp.end());
            if(it->first!=tmp)
                dfs2(y,x,it->first+t.len,it->second);
            else{
                if(it->second-cnt[y]!=0)
                    dfs2(y,x,it->first+t.len,it->second-cnt[y]);
                else{
                    it=prev(it);
                    dfs2(y,x,it->first+t.len,it->second);
                }
            }
        }
    }
    int main(){
        int n,x,y;
        ll len;
        scanf("%d",&n);
        for(int i=1;i<n;++i){
            scanf("%d%d%lld",&x,&y,&len);
            G[x].push_back(Edge(y,len));
            G[y].push_back(Edge(x,len));
        }
        dfs(1,0);
        dfs2(1,0,0,1);
        printf("%lld
    %lld",d,ans);
        return 0;
    }
    View Code

    [NOI2003]逃学的小孩

            最长的路径一定是C->A->B,A->B为直径。先求出一条直径,在从直径上每一个点往外跑到最远,如此枚举。设最长路径为C->A->B,如果这条路径不与直径交,设直径为D->E。那么C->A->D->E显然大于C->A->B。与假设矛盾。如果C->A->B与直径相交,那么把A->B换成直径一定不比原答案差。所以A->B一定为直径。如果树有多条直径,它们一定两两相交,如果考虑走的都是直径,那么似乎分叉点越接近当前直径中点会更优。

    #include<bits/stdc++.h>
    typedef long long ll;
    using namespace std;
    const int N=2e5+10;
    const int M=2e5+10;
    int head[N],ver[2*M],edge[2*M],nex[2*M],tot=1;
    inline void add(int x,int y,int z) {
        ver[++tot]=y,edge[tot]=z,nex[tot]=head[x],head[x]=tot;
    }
    ll d;
    int pre[N],u,v,mid,vis[N];
    ll dfs(int x,int fa){
        ll mm=0,m=0;
        int xx=0,yy=0;
        for(int i=head[x];i;i=nex[i]){
            int y=ver[i];
            if(fa==y)continue;
            ll dis=dfs(y,x)+edge[i];
            if(dis>mm){
                m=mm;yy=xx;
                mm=dis;xx=y;
            }else if(dis>m){
                m=dis;yy=y;
            }
        }
        if(m+mm>d){
            d=m+mm;u=xx;v=yy;mid=x;
        }
        pre[x]=xx;
        return mm;
    }
    ll dfs2(int x){
        vis[x]=1;
        ll mm=0;
        for(int i=head[x];i;i=nex[i]){
            int y=ver[i];
            if(vis[y])continue;
            mm=max(mm,dfs2(y)+edge[i]);
        }
        return mm;
    }
    vector<int> path;
    ll sum[N];
    int main(){
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=0;i<m;++i){
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            add(x,y,z);
            add(y,x,z);
        }
        dfs(1,0);
        vis[mid]=2;
        while(u){
            path.push_back(u);
            vis[u]=2,u=pre[u];
        }
        reverse(path.begin(),path.end());
        path.push_back(mid);
        while(v){
            path.push_back(v);
            vis[v]=2,v=pre[v];
        }
     
        for(int i=1;i<path.size();++i){
            int x=path[i];
            for(int j=head[x];j;j=nex[j])
                if(ver[j]==path[i-1]){
                    sum[i]=sum[i-1]+edge[j];
                    break;
                }
        }
        ll ans=0;
        for(int i=0;i<path.size();++i){
            int x=path[i];
            ll c=dfs2(x);
            ll a=sum[i];
            ll b=sum[path.size()-1]-sum[i];
            ans=max(ans,a+b+c+min(a,b));
        }
        printf("%lld",ans);
        return 0;
    }
    View Code

    [AHOI2008]聚会 

            一定存在一种方案pos->a,pos->b,pos->c没有走重复的边,这种方案即为最佳方案。那么可以枚举两两节点的LCA,取最小值。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=5e5+10;
    const int M=5e5+10;
    int head[N],ver[2*M],nex[2*M],tot=1;
    inline void add(int x,int y) {
        ver[++tot]=y,nex[tot]=head[x],head[x]=tot;
    }
    int d[N],f[N][30],max_t,n;
    void bfs() {
        max_t=log(n)/log(2)+1;
        d[1]=1;
        queue<int> q;
        q.push(1);
        while(!q.empty()) {
            int x=q.front();
            q.pop();
            for(int i=head[x]; i; i=nex[i]) {
                int y=ver[i];
                if(d[y])continue;
                d[y]=d[x]+1;f[y][0]=x;
                for(int i=1; i<=max_t; ++i)
                    f[y][i]=f[f[y][i-1]][i-1];
                q.push(y);
            }
        }
    }
    int lca(int x,int y) {
        if(d[x]<d[y])swap(x,y);
        if(d[x]>d[y])
            for(int i=max_t; i>=0; --i)
                if(d[f[x][i]]>=d[y])
                    x=f[x][i];
        if(x==y)return x;
        for(int i=max_t; i>=0; --i)
            if(f[x][i]!=f[y][i])
                x=f[x][i],y=f[y][i];
        return f[x][0];
    }
    inline int getdis(int x,int y){
        return d[x]+d[y]-2*d[lca(x,y)];
    }
    int main(){
        int m;
        scanf("%d%d",&n,&m);
        for(int i=1;i<n;++i){
            int x,y;
            scanf("%d%d",&x,&y);
            add(x,y);add(y,x);
        }
        bfs();
        while(m--){
            int pos,cost;
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            pos=lca(x,y);
            cost=d[x]+d[y]-2*d[pos]+getdis(z,pos);
            int t=lca(x,z);
            int dd=d[x]+d[z]-2*d[t]+getdis(y,t);
            if(dd<cost){
                pos=t;cost=dd;
            }
            t=lca(y,z);
            dd=getdis(x,t)+d[y]+d[z]-2*d[t];
            if(dd<cost){
                pos=t;cost=dd;
            }
            printf("%d %d
    ",pos,cost);
        }
        return 0;
    }
    View Code
  • 相关阅读:
    Codeforces 1255B Fridge Lockers
    Codeforces 1255A Changing Volume
    Codeforces 1255A Changing Volume
    leetcode 112. 路径总和
    leetcode 129. 求根到叶子节点数字之和
    leetcode 404. 左叶子之和
    leetcode 104. 二叉树的最大深度
    leetcode 235. 二叉搜索树的最近公共祖先
    450. Delete Node in a BST
    树的c++实现--建立一棵树
  • 原文地址:https://www.cnblogs.com/zpengst/p/12299879.html
Copyright © 2011-2022 走看看