📜  门|门CS 2010 |第 43 题(1)

📅  最后修改于: 2023-12-03 15:42:22.434000             🧑  作者: Mango

题目介绍

本题是系列题目的第43题,题目难度为普及+/提高,需要掌握较为扎实的基础知识和算法能力。题目描述如下:

给定一张 $n$ 个点,$m$ 条边的有向图,每个点有一个初始的点权 $w_i$,现在需要进行 $q$ 次操作,每次操作为以下两种之一:

  1. 修改一个点 $u$ 的点权,将其修改为 $k$。
  2. 求从点 $x$ 到点 $y$ 的所有路径中,点权最小值最大的那个点权。

请你编写一个程序,实现以上功能。

方法介绍

本题是一个典型的图论问题,需要运用复杂的算法解决。通常我们会采用以下两种算法来解决此类问题:

  • 利用前缀最小值和后缀最小值求解。
  • 利用二分答案求解。

下面简单介绍一下这两种算法的原理和操作流程:

利用前缀最小值和后缀最小值求解

假设 $g(x)$ 表示从点 $x$ 出发,经过若干条边后到达的节点中点权最小值的最大值。如果 $g(x)$ 比 $g(y)$ 大,那么从点 $x$ 到点 $y$ 的所有路径中,点权最小值的最大值显然为 $g(x)$。

考虑如何求解 $g(x)$。我们可以先将原图反向,然后预处理出从每个点 $x$ 出发,经过若干条边后的最小值 $h(x)$,以及从每个点 $x$ 出发,经过若干条边前的最小值 $f(x)$。这可以通过动态规划实现,具体见代码。

对于一个点 $x$,其 $g(x)$ 的取值显然在 $[f(x), w_x]$ 中,因为如果在从 $x$ 开始的路径上选择了一个小于 $f(x)$ 的点,那么最大能选择的点权必定小于 $f(x)$;而如果选择了大于 $w_x$ 的点,那么最小值必定大于 $w_x$,也是不符合要求的。

因此,我们可以从小到大枚举 $g(x)$ 的取值,然后判断从起点到终点是否有一条路径,使得路径上所有点的点权都不小于当前 $g(x)$ 的取值。具体见代码。

时间复杂度:$O((n+m)qlogw)$,其中 $w$ 为点权的取值范围。

利用二分答案求解

假设 $g(x)$ 表示从点 $x$ 出发,经过若干条边后到达的节点中点权最小值的最大值。同样地,如果 $g(x)$ 比 $g(y)$ 大,那么从点 $x$ 到点 $y$ 的所有路径中,点权最小值的最大值显然为 $g(x)$。

考虑如何二分答案求解 $g(x)$。我们可以建立一个新图,其中每条边 $(u,v)$ 的长度为 $w_v$,然后在新图上从 $x$ 出发,求出到达所有点的最长路,然后将得到的最长路长度与当前的二分答案比较,如果大于等于,则说明解在当前的二分范围内,否则说明解在之前的范围里。

时间复杂度:$O((n+m)qlogw)$,其中 $w$ 为点权的取值范围。

代码实现

利用前缀最小值和后缀最小值求解
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int INF=0x3f3f3f3f;
const int MAXN=10005;

int n,m,q,cnt;
int head[MAXN],hhead[MAXN];
int w[MAXN],f[MAXN],h[MAXN];
bool vis[MAXN];

struct edge{
    int to,val,nxt;
}e[MAXN<<1],he[MAXN<<1];

void add(int u,int v,int val){
    e[++cnt].to=v;
    e[cnt].val=val;
    e[cnt].nxt=head[u];
    head[u]=cnt;
}

void hadd(int u,int v,int val){
    he[++cnt].to=v;
    he[cnt].val=val;
    he[cnt].nxt=hhead[u];
    hhead[u]=cnt;
}

void dfs1(int u,int fa,int minn){
    f[u]=minn;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(v!=fa) dfs1(v,u,min(minn,w[v]));
    }
}

void dfs2(int u,int fa,int maxx){
    h[u]=maxx;
    for(int i=hhead[u];i;i=he[i].nxt){
        int v=he[i].to;
        if(v!=fa) dfs2(v,u,max(maxx,w[v]));
    }
}

bool check(int u,int v,int val){
    vis[u]=1;
    if(u==v) return 1;
    for(int i=head[u];i;i=e[i].nxt){
        int w=e[i].to;
        if(vis[w]||f[w]>val) continue;
        if(check(w,v,val)) return 1;
    }
    for(int i=hhead[u];i;i=he[i].nxt){
        int w=he[i].to;
        if(vis[w]||h[w]<val) continue;
        if(check(w,v,val)) return 1;
    }
    return 0;
}

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&w[i]);
    while(m--){
        int u,v,val;
        scanf("%d%d%d",&u,&v,&val);
        add(u,v,val);
        hadd(v,u,val);
    }
    dfs1(1,0,w[1]);
    dfs2(1,0,w[1]);
    scanf("%d",&q);
    while(q--){
        int op,x,y;
        scanf("%d%d",&op,&x);
        if(op==1){
            scanf("%d",&y);
            w[x]=y;
            memset(vis,0,sizeof(vis));
        }
        else{
            scanf("%d",&y);
            int l=f[x],r=w[x],ans;
            while(l<=r){
                int mid=(l+r)>>1;
                if(check(x,y,mid)){
                    ans=mid; l=mid+1;
                }
                else r=mid-1;
            }
            printf("%d\n",ans);
        }
    }
    return 0;
}
利用二分答案求解
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;

const int INF=0x3f3f3f3f;
const int MAXN=10005;

int n,m,q,cnt,dis[MAXN];
int head[MAXN],w[MAXN];
bool vis[MAXN];

struct edge{
    int to,val,nxt;
}e[MAXN<<1];

void add(int u,int v,int val){
    e[++cnt].to=v;
    e[cnt].val=val;
    e[cnt].nxt=head[u];
    head[u]=cnt;
}

bool check(int s,int t,int val){
    memset(dis,-INF,sizeof(dis));
    memset(vis,0,sizeof(vis));
    queue<int> q;
    q.push(s); dis[s]=0;
    while(!q.empty()){
        int u=q.front(); q.pop(); vis[u]=0;
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].to;
            if(dis[v]<dis[u]+(w[v]>=val)*e[i].val){
                dis[v]=dis[u]+(w[v]>=val)*e[i].val;
                if(!vis[v]){
                    vis[v]=1;
                    q.push(v);
                }
            }
        }
    }
    return dis[t]>=0;
}

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&w[i]);
    while(m--){
        int u,v,val;
        scanf("%d%d%d",&u,&v,&val);
        add(u,v,val);
    }
    scanf("%d",&q);
    while(q--){
        int op,x,y;
        scanf("%d%d",&op,&x);
        if(op==1){
            scanf("%d",&y);
            w[x]=y;
        }
        else{
            scanf("%d",&y);
            int l=1,r=1e9,ans;
            while(l<=r){
                int mid=(l+r)>>1;
                if(check(x,y,mid)){
                    ans=mid; l=mid+1;
                }
                else r=mid-1;
            }
            printf("%d\n",ans);
        }
    }
    return 0;
}

参考文献:https://www.luogu.com.cn/blog/293225/yi-zhang-zong-jie-men-men-cs-2010-di-sec43ti