《算法竞赛进阶指南》图论习题

前言

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

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\n",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\n",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\n",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\n%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\n",pos,cost);
    }
    return 0;
}
View Code

 

posted @ 2020-03-11 14:51  _ZPENG  阅读(244)  评论(0编辑  收藏  举报