洛谷图论题

P1967 [NOIP2013 提高组] 货车运输

首先由于两地之间的路径上的最小权重,为货车的最大载重量,我们希望其比较大。因此,想到了最大生成树,构建一个新的图。由于有可能两地之间不连接,所以判断答案时要用到并查集看看这两个点是否在一个连通块内。由于树内两个点的路径是唯一的,进行LCA。分别对每个连通块进行dfs,获得其深度以及倍增的祖宗节点,和到该祖宗节点路径上的最小边权值。这样,使用LCA时,一边获得其祖宗,一边计算路径上的最小边权,得到结果。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
const ll inf = 1e18;
long long int mod = 1000000007;
const int maxN = 1e4+10;
const int maxM= 5e4+10;
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);

struct Edge{
    int u,v;
    int w;
    bool operator<(const Edge &rhs)const{
        return w>rhs.w;
    }
}edge[maxM];
struct Tree{
    int u,v,next;
    int w;
}tree[2*maxM];
int f[maxN],head[maxN];
int n,m,q,c;
void add(int u,int v,int w){
    tree[++c].u=u;
    tree[c].v=v;
    tree[c].w=w;
    tree[c].next=head[u];
    head[u]=c;
};
int find(int x){
    if(x!=f[x])
        f[x]=find(f[x]);
    return f[x];
}

bool Kruskal(){
    int cnt=0;
    for(int i=1;i<=m;i++){
        if(cnt==n-1)
            break;
        int x=find(edge[i].u);
        int y=find(edge[i].v);
        if(x==y)
            continue;
        add(edge[i].u,edge[i].v,edge[i].w);
        add(edge[i].v,edge[i].u,edge[i].w);
        f[x]=y;
        cnt++;
    }
    if(cnt==n-1)
        return true;
    else
        return false;
}

int depth[maxN], fa[maxN][22], lg[maxN],h[maxN][22],vis[maxN];

void dfs(int now, int fath,int w) {
    fa[now][0] = fath;
    h[now][0]=w;
    vis[now]=1;
    depth[now] = depth[fath] + 1;
    for (int i = 1; i <= lg[depth[now]]; i++) {
        fa[now][i] = fa[fa[now][i - 1]][i - 1];
        h[now][i]=min(h[fa[now][i-1]][i-1],h[now][i-1]);
    }
    for (int i = head[now]; i != 0; i = tree[i].next) {
        if (tree[i].v != fath)
            dfs(tree[i].v, now,tree[i].w);
    }
}
int LCA(int x, int y) {
    if (depth[x] < depth[y])
        swap(x, y);
    int ans=INT_MAX;
    while (depth[x] > depth[y]) {
        int tmp = fa[x][lg[depth[x] - depth[y]]];
        ans=min(ans,h[x][lg[depth[x] - depth[y]]]);
        x=tmp;
    }
    if (x == y){
        return ans;
    }
    for (int k = lg[depth[x]]; k >= 0; k--) {
        if (fa[x][k] != fa[y][k]) {
            ans=min(ans,h[x][k]);
            ans=min(ans,h[y][k]);
            x = fa[x][k];
            y = fa[y][k];
        }
    }
    ans=min(ans,min(h[x][0],h[y][0]));
    return ans;
}


int main() {
    IOS
    cin>>n>>m;
    lg[1]=0;
    for(int i=2;i<=n;i++){
        lg[i]=lg[i/2]+1;
    }
    for(int i=1;i<=m;i++){
        int x,y,z;
        cin>>x>>y>>z;
        edge[i].u=x,edge[i].v=y;
        edge[i].w=z;
    }
    sort(edge+1,edge+1+m);
    for(int i=1;i<=n;i++){
        f[i]=i;
    }
    Kruskal();
    for(int i=1;i<=n;i++){
        if(vis[i]==0)
            dfs(i,0,0);
    }
    cin>>q;
    for(int i=1;i<=q;i++){
        int x,y;
        cin>>x>>y;
        int fx=find(x);
        int fy=find(y);
        if(fx!=fy){
            cout<<-1<<endl;
            continue;
        }
        cout<<LCA(x,y)<<endl;
    }
    return 0;
}

P1613 跑路

题目要求到1到n点的最短时间,但是只给了每条路径长度为1。而每次跑2的t次方米只要一秒,所以需要把图转成每个点之间到达所花费的时间作为边权。利用倍增思想p[u][v][0]表示点u到点v存在2的0次方米的路径。再利用Floyd计算每个点到达其他的点的时间,构成图

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int maxN = 1e3 + 10;
const int maxM = 1e6 + 10;
const int inf = 1e6;

int n,m;
int p[55][55][34];

int main() {
    cin>>n>>m;
    vector<vector<int>>a(n+1,vector<int>(n+1,inf));
    for(int i=1;i<=m;i++){
        int u,v;
        cin>>u>>v;
        a[u][v]=1;
        p[u][v][0]=1;
    }
    for(int t=1;t<=32;t++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                for(int k=1;k<=n;k++){
                    if(p[i][j][t-1]==1&&p[j][k][t-1]==1){
                        p[i][k][t]=1;
                        a[i][k]=1;
                    }
                }
            }
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            for(int k=1;k<=n;k++){
                a[i][j]=min(a[i][j],a[i][k]+a[k][j]);
            }
        }
    }
    cout<<a[1][n]<<endl;
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值