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
  • 相关阅读:
    ZOJ 3529
    将博客搬至CSDN
    BST 增删查操作 递归/非递归实现
    容器vector容量翻倍增长策略效率分析
    整数分解为若干项之和
    PAT-B-1080 MOOC期终成绩
    最大公约数 + 最小公倍数
    Fibonacci数
    排序
    PAT-B-1020
  • 原文地址:https://www.cnblogs.com/zpengst/p/12299879.html
Copyright © 2011-2022 走看看