Graph(A-E:拓扑 F-J:MST K-O:最短路)

https://vjudge.net/contest/312213#overview password zut

A:http://poj.org/problem?id=2367 

给邻接矩阵,跑出任一拓扑序

#include<iostream>
#include<queue>
#include<vector>
using namespace std;
#define LL long long
#define FI first
#define SE second
#define MP make_pair
#define PII pair<int,int>
const LL mod = 1e9+7;
const int MX = 1e3+5;
vector<int>g[MX];int in[MX];
int main(){
    int n;cin>>n;
    for(int i=1;i<=n;i++){
        int x;while(1){cin>>x;if(x==0)break;g[i].push_back(x);in[x]++;}
    }
    queue<int>q;
    for(int i=1;i<=n;i++){
        if(in[i]==0)q.push(i);
    }
    while(!q.empty()){
        int now=q.front();q.pop();cout<<now<<' ';
        for(int i=0;i<(int)g[now].size();i++){
            in[g[now][i]]--;
            if(in[g[now][i]]==0)q.push(g[now][i]);
        }
    }
    return 0;
}

B:http://acm.hdu.edu.cn/showproblem.php?pid=1285

题意:

给n个队伍,有m个比较,每个比较包括u和v,代表u排名在v之前,求原始的排名,此排名不唯一,输出字典序最小。

思路:

对于每个比较,建立一条u指向v的边,然后拓扑序就是排名

要求字典序,那么将队列换成优先队列,把入度为0的全部丢进优先队列,跑拓扑。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define FI first
#define SE second
#define MP make_pair
#define PII pair<int,int>
const LL mod = 1e9+7;
const int MX = 1e6+5;
vector<int>g[MX];int in[MX];
int main(){
    int n,m;
    while(cin>>n>>m){
        for(int i=1;i<=n;i++)in[i]=0,g[i].clear();
        for(int i=1;i<=m;i++){
            int u,v;cin>>u>>v;
            g[u].push_back(v);in[v]++;
        }
        priority_queue<int,vector<int>,greater<int> >q;
        vector<int>ans;
        for(int i=1;i<=n;i++){
            if(in[i]==0)q.push(i);
        }
        while(!q.empty()){
            auto now=q.top();q.pop();
            ans.push_back(now);
            for(auto i:g[now]){
                in[i]--;
                if(in[i]==0)q.push(i);
            }
        }
        for(int i=0;i<(int)ans.size()-1;i++)cout<<ans[i]<<' ';
        cout<<ans[ans.size()-1];
        puts("");
    }
    return 0;
}

C:http://acm.hdu.edu.cn/showproblem.php?pid=3342 //拓扑排序判环

题意:

给一个有向图,问是否有环。

思路:

跑拓扑之后,看看是否还有剩余的点。(剩余的点入度不为0)

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define FI first
#define SE second
#define MP make_pair
#define PII pair<int,int>
const LL mod = 1e9+7;
const int MX = 1e6+5;
vector<int>g[MX];int in[MX];
int main(){
    int n,m;
    while(cin>>n>>m){
        if(n==0)break;
        for(int i=0;i<=n;i++)in[i]=0,g[i].clear();
        for(int i=1;i<=m;i++){
            int u,v;cin>>u>>v;
            g[u].push_back(v);in[v]++;
        }
        queue<int>q;
        for(int i=0;i<=n;i++){
            if(in[i]==0)q.push(i);
        }
        while(!q.empty()){
            auto now=q.front();q.pop();n--;
            for(auto i:g[now]){
                in[i]--;
                if(in[i]==0)q.push(i);
            }
        }
        (n==-1)?puts("YES"):puts("NO");
    }
    return 0;
}

D:https://codeforces.com/problemset/problem/1131/D //并查集缩点+拓扑排序

题意:

未知a数组和b数组,长度是m和n,给一个表示大小关系的矩阵 ,xij表示ai和bj的大小关系,求出每个元素最小的a和b(最小从1开始)。

思路:

等于关系的可以缩成一个点,然后偏序关系转换成有向边,生成的图可能会是森林,不是一棵树。

所有缩点上进行拓扑排序,注意此拓扑序不是普通的拓扑序,有点像逆bfs,也就是不同的点的拓扑序有可能是相同的值。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define FI first
#define SE second
#define MP make_pair
#define PII pair<int,int>
const LL mod = 1e9+7;
const int MX = 2e3+5;
vector<int>g[MX];int in[MX];int fa[MX];
char mp[MX][MX];int ans[MX];
int found(int x){
    if(fa[x]==x)return x;
    return fa[x]=found(fa[x]);
}
int main(){
    int n,m;cin>>n>>m;
    for(int i=0;i<n+m;i++)fa[i]=i;
    for(int i=0;i<n;i++)cin>>mp[i];
    for(int i=0;i<n;i++){//=的缩点
        for(int j=0;j<m;j++){
            if(mp[i][j]=='='){
                int fi=found(i),fj=found(j+n);
                if(fi!=fj)fa[fj]=fa[fi];
            }
        }
    }
    for(int i=0;i<n;i++){//其他的再缩点上连边
        for(int j=0;j<m;j++){
            int fi=found(i),fj=found(j+n);
            if(mp[i][j]=='<'){
                g[fi].push_back(fj);in[fj]++;
            }
            if(mp[i][j]=='>'){
                g[fj].push_back(fi);in[fi]++;
            }
        }
    }
    set<int>ss;//去重
    for(int i=0;i<n+m;i++){
        int fi=found(i);
        if(in[fi]==0)ss.insert(fi);
    }
    queue<pair<int,int> >q;//拓扑标记
    for(auto i:ss)q.push(MP(i,0)),ans[i]=0;
    while(!q.empty()){
        auto now=q.front();q.pop();
        ans[now.FI]=now.SE+1;
        for(auto i:g[now.FI]){
            in[i]--;
            if(in[i]==0)q.push(MP(i,now.SE+1));
        }
    }
    for(int i=0;i<n+m;i++)if(ans[found(i)]==0){puts("No");return 0;}
    puts("Yes");
    for(int i=0;i<n;i++) cout<<ans[found(i)]<<' ';
    cout<<endl;
    for(int i=n;i<m+n;i++)cout<<ans[found(i)]<<' ';
    return 0;
}

E:https://blog.csdn.net/qq_41730604/article/details/96453763 //二分+拓扑排序

F:http://acm.hdu.edu.cn/showproblem.php?pid=1102 

给邻接矩阵,求最小生成树的费用。克鲁斯卡尔

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define FI first
#define SE second
#define MP make_pair
#define PII pair<int,int>
const LL mod = 1e9+7;
const int MX = 1e2+5;
struct no{int u,v,c;};
bool operator<(const no &x,const no &y){return x.c<y.c;}
int fa[MX],mp[MX][MX];
vector<no>e;
int found(int x){if(x==fa[x])return x;return fa[x]=found(fa[x]);}
int main(){
    int n;while(cin>>n){
        for(int i=1;i<=n;i++)fa[i]=i;
        e.clear();
        for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)scanf("%d",&mp[i][j]);
        int q;cin>>q;while(q--){
            int uu,vv;scanf("%d%d",&uu,&vv);
            mp[uu][vv]=-1;mp[vv][uu]=-1;
            int fu=found(uu),fv=found(vv);
            if(fu!=fv){fa[fu]=fa[fv];}
        }
        for(int i=1;i<=n;i++)
            for(int j=i+1;j<=n;j++)
                if(mp[i][j]!=-1)
                    e.push_back(no{i,j,mp[i][j]});
        sort(e.begin(),e.end());
        int ans=0;
        for(auto i:e){
            int fu=found(i.u),fv=found(i.v);
            if(fu!=fv){fa[fu]=fa[fv];ans+=i.c;}
        }
        printf("%d\n",ans);
    }
    return 0;
}

G:http://poj.org/problem?id=2377

题意:求最大生成树

思路:将每条边的wi变成-wi,跑克鲁斯卡尔

#include  <map>
#include  <set>
#include  <cmath>
#include  <queue>
#include  <cstdio>
#include  <vector>
#include  <cstring>
#include  <iostream>
#include  <climits>
#include  <algorithm>
using namespace std;
#define LL long long
#define FI first
#define SE second
#define MP make_pair
#define PII pair<int,int>
const LL mod = 1e9+7;
const int MX = 1e2+5;
struct no{int u,v,c;};
bool operator<(const no &x,const no &y){return x.c<y.c;}
int fa[MX],mp[MX][MX];
vector<no>e;
int found(int x){if(x==fa[x])return x;return fa[x]=found(fa[x]);}
int main(){
    int n,m;cin>>n>>m;
    for(int i=1;i<=n;i++)fa[i]=i;
    for(int i=1;i<=m;i++){
        int u,v,w;scanf("%d%d%d",&u,&v,&w);
        e.push_back(no{u,v,-w});
    }
    sort(e.begin(),e.end());
    int ans=0;
    for(int i=0;i<(int)e.size();i++){
        int fu=found(e[i].u),fv=found(e[i].v);
        if(fu!=fv){fa[fu]=fa[fv];ans+=e[i].c;}
    }
    for(int i=2;i<=n;i++)if(found(i)!=found(i-1)){puts("-1");return 0;}
    printf("%d\n",-ans);
    return 0;
}

H:http://poj.org/problem?id=2395

题意:求最小生成树(此费用不是总和,是生成树的权值最大的边的权值)

思路1:二分这个最大权值,然后看小于等于这个权值的边能不能组成联通图。

思路2:其实不用二分。。。。克鲁斯卡尔跑的时候保证是权值最小的。

#include  <map>
#include  <set>
#include  <cmath>
#include  <queue>
#include  <cstdio>
#include  <vector>
#include  <cstring>
#include  <iostream>
#include  <climits>
#include  <algorithm>
using namespace std;
#define LL long long
#define FI first
#define SE second
#define MP make_pair
#define PII pair<int,int>
const LL mod = 1e9+7;
const int MX = 1e6+5;
struct no{int u,v,c;};
bool operator<(const no &x,const no &y){return x.c<y.c;}
int fa[MX];
vector<no>e;int n,m;
int found(int x){if(x==fa[x])return x;return fa[x]=found(fa[x]);}
bool check(int x){
    for(int i=1;i<=n;i++)fa[i]=i;
    for(int i=0;i<(int)e.size();i++){
        if(e[i].c>x)continue;
        int fx=found(e[i].u),fy=found(e[i].v);
        if(fx!=fy) fa[fx]=fa[fy];
    }
    for(int i=2;i<=n;i++)if(found(i)!=found(i-1))return 0;
    return 1;
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int u,v,w;scanf("%d%d%d",&u,&v,&w);e.push_back(no{u,v,w});
    }
    sort(e.begin(),e.end());
    int l=1,r=1e9,ans;
    while(l<=r){
        int mid=(l+r)/2;
        if(check(mid)) ans=mid,r=mid-1;
        else l=mid+1;
    }
    cout<<ans;
    return 0;
}

I:https://codeforces.com/problemset/problem/1095/F

题意:

给顶点数n和m,然后m条信息,每条信息包括,ui,vi,wi,表现ui和vi直接添加一条无向边需要花费wi。另外的:任意ui和vi可以添加一条边,代价为a[ui]+a[vi]。求最小生成树。

思路:

假设把后者加边方式的n*(n-1)/2条边算入,克鲁斯卡尔的过程中,只会轮到a[i]最小的边和其他的边连接,其他的轮不到。

那么将前者m条边 和 1连其他点的n-1条边 跑克鲁斯卡尔即可。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define FI first
#define SE second
#define MP make_pair
#define PII pair<int,int>
const LL mod = 1e9+7;
const int MX = 2e5+5;
LL a[MX];
int fa[MX];
struct no{int u,v;LL w;};
bool operator<(const no &x,const no &y){return x.w<y.w;}
vector<no>e;
int found(int x){if(fa[x]==x)return x;return fa[x]=found(fa[x]);}
int main(){
    int n,m;cin>>n>>m;
    for(int i=1;i<=n;i++)fa[i]=i;
    LL mi=LLONG_MAX;int u;
    for(int i=1;i<=n;i++){
        scanf("%I64d",&a[i]);if(a[i]<mi)mi=a[i],u=i;
    }
    for(int i=1;i<=m;i++){
        int u,v;LL w;
        scanf("%d%d%I64d",&u,&v,&w);
        e.push_back(no{u,v,w});
    }
    for(int i=1;i<=n;i++){
        if(i==u)continue;
        e.push_back(no{i,u,a[i]+a[u]});
    }
    sort(e.begin(),e.end());
    LL ans=0;
    for(auto i:e){
        int fu=found(i.u),fv=found(i.v);
        if(fu!=fv)ans+=i.w,fa[fu]=fa[fv];
    }
    cout<<ans;
    return 0;
}

J:https://codeforces.com/problemset/problem/1108/F 

题意:

给一个联通simpy图,此时最小生成树可能不唯一,你可以给每条边加权值,使得最小生成树唯一,求加的权值的和最小是多少。

思路:

考虑克鲁斯卡尔的过程,处理同一wi的边的时候,有可能有多种选择使得处理完这个wi后效果相同。那么将这些多余的wi加上1,那么到处理wi+1的时候,这些边就是无效边了。

也就是说,处理同一wi的边的时候,如果遇到在同一联通的两个点时,如果这个联通块是先前同一wi处理出来的联通块,那么这条边就需要+1,就不用再管这条边了。

克鲁斯卡尔的过程唯一,那么最小生成树就是唯一了。

怎么判断是否是同一wi时候碰上的冲突,先剔除掉已经联通的边的两点的边,剩下的就是要wi时联通的,那么冲突就可以知道肯定是当前wi发生的冲突。

#include<bits/stdc++.h>
//怎么判断是否是同一w时候删的,先剔除掉已经联通的,剩下的就是要w时联通的,那么冲突就可以知道肯定是当前w发生的冲突。
using namespace std;
#define LL long long
#define FI first
#define SE second
#define MP make_pair
#define PII pair<int,int>
const LL mod = 1e9+7;
const int MX = 2e5+5;
int fa[MX];
int found(int x){if(fa[x]==x)return x;return fa[x]=found(fa[x]);}
map<int,vector<PII>>ma;
int main(){
    int n,m;cin>>n>>m;
    for(int i=1;i<=n;i++)fa[i]=i;
    for(int i=1;i<=m;i++){
        int u,v,w;scanf("%d%d%d",&u,&v,&w);
        ma[w].push_back(MP(u,v));
    }
    int ans=0;
    for(auto i:ma){
        for(auto j:i.SE){
            int fx=found(j.FI),fy=found(j.SE);
            if(fx==fy)ans--;
        }
        for(auto j:i.SE){
            int fx=found(j.FI),fy=found(j.SE);
            if(fx==fy)ans++;
            else fa[fx]=fa[fy];
        }
    }
    cout<<ans;
    return 0;
}

K:http://acm.hdu.edu.cn/showproblem.php?pid=2544

花式最短路。(我的dijkstra O(ElogV))

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define FI first
#define SE second
#define MP make_pair
#define PII pair<int,int>
const LL mod = 1e9+7;
const int MX = 1e2+5;
struct no{int c,to;};
vector<no>g[MX];
bool operator<(const no &x,const no &y) {return x.c>y.c;}
int dis[MX];
int main(){
    int n,m;
    while(cin>>n>>m){
        if(n==0)break;
        for(int i=1;i<=n;i++)dis[i]=INT_MAX/2,g[i].clear();
        while(m--){
            int u,v,c;scanf("%d%d%d",&u,&v,&c);
            g[u].push_back(no{c,v});
            g[v].push_back(no{c,u});
        }
        priority_queue<no>q;
        dis[1]=0;q.push(no{0,1});
        while(!q.empty()){
            int nowv=q.top().to;q.pop();
            for(auto i:g[nowv]){
                if(i.c+dis[nowv]<dis[i.to]){
                    dis[i.to]=i.c+dis[nowv];
                    q.push(no{dis[i.to],i.to});
                }
            }
        }
        printf("%d\n",dis[n]);
    }
    return 0;
}

L:http://poj.org/problem?id=3268

题意:n头牛在n个不同的地方,去x开party,然后回家,求n头牛中路程最长的(他们很聪明,会选择最短路)。

思路:先求x的单源最短路,然后将所有的边反向,再求x 的单源最短路。然后对于第i头牛,他的路程就是两次x到i的距离的和。

#include  <map>
#include  <set>
#include  <cmath>
#include  <queue>
#include  <cstdio>
#include  <vector>
#include  <cstring>
#include  <iostream>
#include  <climits>
#include  <algorithm>
using namespace std;
#define LL long long
#define FI first
#define SE second
#define MP make_pair
#define PII pair<int,int>
const LL mod = 1e9+7;
const int MX = 1e5+5;
struct no{int co,to;};
bool operator<(const no &x,const no &y){return x.co>y.co;}
vector<no>g[MX];
int dis[MX][2];
int V[MX][2],c[MX];
int main(){
    int n,m,x;cin>>n>>m>>x;
    for(int i=1;i<=m;i++)scanf("%d%d%d",&V[i][0],&V[i][1],&c[i]);
    for(int k=0;k<=1;k++){
        for(int i=1;i<=n;i++)g[i].clear(),dis[i][k]=INT_MAX/2;
        for(int i=1;i<=m;i++)g[V[i][k]].push_back(no{c[i],V[i][k^1]});
        priority_queue<no>q;
        dis[x][k]=0;q.push(no{0,x});
        while(!q.empty()){
            int nowv=q.top().to;q.pop();
            for(int i=0;i<(int)g[nowv].size();i++){
                if(g[nowv][i].co+dis[nowv][k]<dis[g[nowv][i].to][k]){
                    dis[g[nowv][i].to][k]=g[nowv][i].co+dis[nowv][k];
                    q.push(no{dis[g[nowv][i].to][k],g[nowv][i].to});
                }
            }
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++)ans=max(dis[i][0]+dis[i][1],ans);
    cout<<ans<<endl;
    return 0;
}

M:http://poj.org/problem?id=3259 //负环

判断给定图是否有负环,有负环,那么小明从某个点出发,可能回到过去。

spfa,某个点入队次数>n,说明有负环

#include  <map>
#include  <set>
#include  <cmath>
#include  <queue>
#include  <cstdio>
#include  <vector>
#include  <cstring>
#include  <iostream>
#include  <climits>
#include  <algorithm>
using namespace std;
const int max_n=505;
struct no{int to,cos;};
vector<no>g[max_n];
int d[max_n],inq[max_n];
int vis[max_n];int n,m,q;
int spfa(int s,int t){    //如果入队次数大于n+1,那么有负环(自己手写)
    queue<int>q;
    q.push(s),d[s]=0,inq[s]=1;vis[s]++;
    while(!q.empty()){
        int now=q.front();
        for(int i=0;i<(int)g[now].size();i++){
            int v=g[now][i].to;
            if(d[v]>d[now]+g[now][i].cos){ //松弛操作
                d[v]=d[now]+g[now][i].cos;
                if(inq[v])continue;
                inq[v]=1;
                q.push(v);
                vis[v]++;
                if(vis[v]>n+1) return -1;
            }
        }
        q.pop();
        inq[now]=0;
    }
    if(d[t]==INT_MAX/2) return -2;
    return d[t];
}

int main(){
    int T;cin>>T;while(T--){
        cin>>n>>m>>q;
        for(int i=1;i<=n;i++)d[i]=INT_MAX/2,inq[i]=0,g[i].clear(),vis[i]=0;
        for(int i=1;i<=m;i++){
            int u,v,w;scanf("%d%d%d",&u,&v,&w);
            g[u].push_back(no{v,w});
            g[v].push_back(no{u,w});
        }
        for(int i=1;i<=q;i++){
            int u,v,w;scanf("%d%d%d",&u,&v,&w);
            g[u].push_back(no{v,-w});
        }
        if(spfa(1,n)==-1)puts("YES");
        else puts("NO");
    }
    return 0;
}

N:https://codeforces.com/problemset/problem/1076/D //最短路树

题意:给一个联通simple图,问,删到留下小于等于k条边后,最多有多少个点到1的最短距离和没删之前一样,并输出这k条边。

思路:跑dijkstra的时候记录每个点最后的前驱,处理出一个最短路树,然后从1点bfs出k条边。

为什么会有最短路树(脑补dijkstra过程)

dijkstra的时候,每次都会处理出一个点,使这个点加入到已经处理好的集合中(这个点到1的最短路径已经确定)。

每次记录的是每个点的最后的前驱,那么肯定是保证每个点是有前驱。

最后这个通过前驱处理出来的图是联通的。

 

之前的dijkstra都是不完整的啊!!,没有维护那个最短路已确定的点的集合。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define FI first
#define SE second
#define MP make_pair
#define PII pair<int,int>
const LL mod = 1e9+7;
const int MX = 1e6+5;
struct no{LL c,to;};
vector<no>g[MX];
bool operator<(const no &x,const no &y) {return x.c>y.c;}
LL dis[MX];
int pre[MX],ein[MX],vis[MX];
vector<int>G[MX];
map<PII,int>ma;
int main(){
    int n,m,k;cin>>n>>m>>k;
    for(int i=1;i<=n;i++)dis[i]=LLONG_MAX/2;//!!!
    for(int i=1;i<=m;i++){
        int u,v,c;scanf("%d%d%d",&u,&v,&c);
        g[u].push_back(no{c,v});ma[MP(u,v)]=i;
        g[v].push_back(no{c,u});ma[MP(v,u)]=i;
    }
    priority_queue<no>q;
    dis[1]=0;q.push(no{0,1});
    while(!q.empty()){
        int nowv=q.top().to;q.pop();
        if(vis[nowv])continue;
        vis[nowv]=1;
        for(auto i:g[nowv]){
            if(i.c+dis[nowv]<dis[i.to]){
                pre[i.to]=nowv;ein[i.to]=ma[MP(i.to,nowv)];
                dis[i.to]=i.c+dis[nowv];
                q.push(no{dis[i.to],i.to});
            }
        }
    }
    for(int i=2;i<=n;i++) G[pre[i]].push_back(i);
    int zz=min(n-1,k); cout<<zz<<endl;
    queue<int>Q;Q.push(1);
    while(!Q.empty()){
        int now=Q.front();Q.pop();
        for(auto i:G[now]){
            if(k==0)return 0;//!!!
            cout<<ein[i]<< ' ',Q.push(i), k--;
        }

    }
    return 0;
}

O:https://blog.csdn.net/qq_41730604/article/details/96628900 //lca+最短路

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值