zoukankan      html  css  js  c++  java
  • Day 5 下午

    内容提要

    图论:

    dijkstra

    拓扑排序

    最近公共祖先


    Dijkstra

    无负权的图能不能在 SSP 上更高效?

    •   d(u) 被更新成真实值之前, u 出边的松弛操作都无意义
    •   前面的算法中不知道谁是真实值,但是这里没有负权,所以我们每次用最小的边更新其他的

    Intuitive ideas

    •   维护 Queue,表示 Queue 里面点的出边都被松弛过了
    •   找出不在 Queue 中的, d(u) 最小的 u
    •   松弛所有 u 的出边
    •   将 u 加入 Queue后,在 Queue 中的点 d 不会再更新

    那么问题来了,怎么找到不在 Queue 中 d(u) 最小的 u???

    暴力扫描所有点?

    代码:

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 1e5 + 5;
    const int inf = 1 << 29;
    
    struct edge{
        int u, v, w;
    };
    vector<edge> edg[N];
    int d[N], n, m, S;
    
    bool relaxed[N];//一个点在不在队列里 
    
    void add(int u, int v, int w)
    {
        edg[u].push_back((edge){u, v, w});
    }
    int main()
    {
        cin >> n >> m >> S;
        for (int i = 1; i <= n; i++) d[i] = inf;
        for (int u, v, w, i = 1; i <= m; i++)
            cin >> u >> v >> w, add(u, v, w);
            
        d[S] = 0;
        for (int i = 1; i <= n; i++)
        {
            int u = 1; while (relaxed[u]) ++u;//找到1~n中第一个不在队列中的 
            for (int j = 1; j <= n; j++)//找到不在队列当中的最小的u 
                if (!relaxed[j] && d[j] < d[u]) u = j;
            //find a node u not relaxed yet with least d(u)
            relaxed[u] = true;//把它标记为在队列中 
            for (int e = 0; e < edg[u].size(); e++)
            {
                int v = edg[u][e].v, w = edg[u][e].w;
                d[v] = min(d[v], d[u] + w);//枚举松弛 
            }
        }
        for (int i = 1; i <= n; i++)
            cout << d[i] << endl;
    }

    时间复杂度 O(n^2 + m),有点高吖

    可以用小根堆 PQueue 维护

      一旦 d(u) 被更新,就将 u 插入 PQueue,关键字为 d(u)

      如何删除 PQueue 中以未更新前的 d(u) 为关键字的节点?

        不需要删除!因为以更新后的 d(u) 为关键字的节点会一定先出堆,用Bool 数组记录每个点的出边是否松弛过了,当 PQueue 的堆顶 u 在Bool 数组中被标记后,不用再重复松弛

    代码:

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 1e5 + 5;
    const int inf = 1 << 29;
    
    struct edge{
        int u, v, w;
    };
    vector<edge> edg[N];
    int d[N], n, m, S;
    
    bool relaxed[N];
    struct Qnode {
        int u, du;
        bool operator<(const Qnode &v)
            const {return v.du < du;}//当v的优先级比现在的更小的时候,把它放在上面 
    };
    priority_queue<Qnode> PQueue;
    
    void add(int u, int v, int w)
    {
        edg[u].push_back((edge){u, v, w});
    }
    int main()
    {
        cin >> n >> m >> S;
        for (int i = 1; i <= n; i++) d[i] = inf;
        for (int u, v, w, i = 1; i <= m; i++)
            cin >> u >> v >> w, add(u, v, w);
            
        d[S] = 0; PQueue.push((Qnode){S, 0});
        while (!PQueue.empty())
        {
            int u = PQueue.top().u; PQueue.pop();
            if (relaxed[u]) continue;//如果所有出边都被松弛过就跳出 
                //if edges staring from u are already relaxed, no need to relax again.
            relaxed[u] = true;
            for (int e = 0; e < edg[u].size(); e++)
            {
                int v = edg[u][e].v, w = edg[u][e].w;
                if (d[v] > d[u] + w)
                {
                    d[v] = d[u] + w;
                    PQueue.push((Qnode){v, d[v]});
                    //if d(v) is updated, push v into PQueue
                }
            }
        }
        for (int i = 1; i <= n; i++)
            cout << d[i] << endl;
    }

    一些 PQueue 的细节

      c++ 模板库中已经有 priority_queue 实现好了堆

      加入一个元素 p.push(a)

      访问堆 p 的顶 p.top()

      弹出堆 p 的顶 p.pop()

      判断 p 是否为空 p.empty()

      如何定义堆中大小比较顺序:可以参考示例代码的写法

    Time Complexity

    每条边只会进行一次松弛

    PQueue 压入 u,意味着 d(u) 被更新

    时间复杂度 O(m +  mlog n)

    DAG
    G 是一个有向无环图,则称为 DAG
    DAG 不要求弱连通

    拓扑排序

    n 个点的 DAG 一定存在拓扑序列

    P 是 G 的拓扑序,当且仅当

    • P 是 n 阶排列
    • 若 G 中存在 u --> v 的路径,排列 P 中 u 一定出现在 v 的前

    显然拓扑序也是不一定唯一的,并且数量是指数(至少也是阶乘)级别的

    证明任何一个DAG一定有拓扑序

    证明:

    1.任何一个DAG一定有出度为0的点

    2.出度为0的点放在拓扑序的最后一定没有问题,然后把这个点和他的边删除

    3.不断操作,最后得到拓扑序

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 1e5 + 5;
    const int inf = 1 << 29;
    
    struct edge{
        int u, v;
    };
    vector<edge> edg[N];
    int n, m, outdeg[N], ans[N];
    
    queue<int> Queue;
    void add(int u, int v)
    {
        edg[u].push_back((edge){u, v});
    }
    int main()
    {
        cin >> n >> m;
        for (int u, v, i = 1; i <= m; i++)
            cin >> u >> v, add(v, u), outdeg[u]++;//出度++ 
            
        for (int i = 1; i <= n; i++)
            if (outdeg[i] == 0) Queue.push(i);
        for (int i = 1; i <= n; i++)
        {
            if (Queue.empty())//如果空了,说明肯定不是dag 
                {printf("Not DAG"); return 0;}
            int u = Queue.front(); Queue.pop(); ans[n - i + 1] = u;
            for (int e = 0; e < edg[u].size(); e++)
            {
                int v = edg[u][e].v;
                if (--outdeg[v] == 0) Queue.push(v);
            }//把指向u的点删掉,出度--,然后判断出度是否为0 
        }
    }

    如何求DAG的拓扑排序数量?

    无法做到

    哈哈哈~

    最近公共祖先


    以 r 为根的有根树上,若 a 在 r 到 x 的路径上,称 a 为 x 的祖先

    若 a 是 x 和 y 共同的祖先,则称 a 是 x 和 y 的公共祖先

    x 和 y 所有公共祖先中,离他们最近的称为 x 和 y 的最近公共祖

    先,记为 lca(x,y)

    先来看一个最暴力的Floyd写法(O(n^3))

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int maxn = 105;
    const int inf = 1 << 29;
    int dis[maxn][maxn], n, m, Q;
    int main()
    {
        cin >> n >> m >> Q;
        for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            if (i != j) dis[i][j] = inf;
        for (int i = 1, u, v, w; i <= m; i++)
            cin >> u >> v >> w,
            dis[u][v] = min(dis[u][v], w),
            dis[v][u] = min(dis[v][u], w);
        
        
        for (int k = 1; k <= n; k++)
        for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            dis[i][j] = min(dis[i][j], max(dis[i][k], dis[k][j]));
            
        for (int u, v, i = 1; i <= Q; i++)
            cin >> u >> v, cout << dis[u][v] << endl;
    }
    
    /*Problem statement
    Given a graph G containing n nodes and m bidirectional edges.
    The weight of a path p0->p1->p2->...->pt is the maximum weight of edges on the path.
    There are Q queries given (u, v), output the minimum weight of paths from u to v.
    Guarantee between any nodes u & v , there is at least one path from u to v.
    */

    显然非常容易tle

    还有其他方法吗?

    树上倍增!

    O(log n) 询问两点 LCA

    Algorithms for Least Common Ancestors

    RMQ with Euler sequence

    Initialization
    记录 dep(x) 表示 x 到树根的距离

    预处理 anc[x][j] 表示 x 在树上向上 2^j 层的祖先

    anc[x][0] = parent(x)

    anc[x][j] = anc[anc[x][j − 1]][j − 1]

    寻找 x 的 k 层祖先

    写出 k 的二进制表示 k = 2^e0 + 2^e1 + · · · + 2^et

    kth(x) = anc[anc[anc[anc[......][e3]][e2]][e1]][e0]

    代码:

    #include<bits/stdc++.h>
    
    using namespace std;
    long long read(){
        long long ans=0;
        char last=' ',ch=getchar();
        while(ch<'0' || ch>'9')last=ch,ch=getchar();
        while(ch>='0' && ch<='9')ans=ans*10+ch-'0',ch=getchar();
        if(last=='-')ans=-ans;
        return ans;
    } 
    
    const int maxn=500010;
    int n,m,s,anc[maxn][21],dep[maxn];
    
    struct edge
    {
        int u,v;
    }edg[maxn];
    
    vector<edge> adj[maxn];
    
    void dfs(int u)
    {
        for(int j=1;j<20;j++)
            anc[u][j]=anc[anc[u][j-1]][j-1];
        for(int i=0;i<adj[u].size();i++)
        {
            int v=adj[u][i].v;
            if(v!=anc[u][0])
                dep[v]=dep[u]+1,anc[v][0]=u,dfs(v);
        }
    }
    
    int solve(int u,int v)
    {
        if(dep[u]<dep[v]) swap(u,v);
        for(int d=dep[u]-dep[v],j=0;d;d>>=1,j++)
            if(d&1) u=anc[u][j];
        if(u==v) return u;
        for(int j=19;j>=0;j--)
            if(anc[u][j]!=anc[v][j])
                u=anc[u][j],v=anc[v][j];
        return anc[u][0];
    }
    
    int main()
    {
        n=read(),m=read(),s=read();
        for(int u,v,i=1;i<n;i++)
        {
            u=read(),v=read();
            edg[i]=(edge){u,v};
        }
        for (int i = 1; i <= m; i++)
        {
            int u = edg[i].u, v = edg[i].v;
            adj[u].push_back(edg[i]),
            adj[v].push_back((edge){v, u});
        }
            
        dfs(s);
        for(int u,v,i=1;i<=m;i++)
        {
            u=read(),v=read();
            printf("%d
    ",solve(u,v));
        }
    }
                                                                                           

    对应的就是洛谷P3379

     (洛谷的题怎么都这么喜欢卡常呢QwQ

    Exercise
    给定 n 个点的带边权树, Q 次询问,每次给定 x 和 y,询问 x 到 y的距离。
    数据范围 n; Q ≤ 10^5
    Solution
    以 1 为根, DFS 时记录 len(x) 表示 x 到根的距离
    设 t = lca(x; y), x --> y 的路径可以拆程 x --> t 和 t --> y 两段
    答案就是 len(x) + len(y) − 2len(t)


    Exercise
    给定 n 个点 m 条边带边权的无向连通图, Q 次询问,每次给定 x和 y,询问 x 到 y 的所有路径中最大边权的最小值。
    数据范围 n; Q ≤ 10^5
    Solution
    x --> y 的答案一定在 MST 上
    在 MST 上询问 x --> y 上的最大边权
    倍增时多记一项 maxw[x][j],表示 x --> anc[x][j] 上的最大边权
    maxw[x][j] = max{maxw[x][j − 1]; maxw[anc[x][j − 1]][j − 1]}

    dijkstra加倍增做法:

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int maxn = 1000005;
    struct edge {
        int u, v, w;
    }edg[maxn];
    int n, m, p[maxn], Q, dep[maxn];
    vector<edge> adj[maxn]; // edges in MST
    bool cmp(edge a, edge b)
        {return a.w < b.w;}
    int findp(int t) 
        {return p[t] ? p[t] = findp(p[t]) : t;}
        
    bool merge(int u, int v)
    {
        u = findp(u); v = findp(v);
        if (u == v) return false;
        p[u] = v; return true;
    }
    
    int anc[maxn][20], maxw[maxn][20];
    void dfs(int u)
    {
        for (int j = 1; j < 20; j++)
            anc[u][j] = anc[anc[u][j - 1]][j - 1],
            maxw[u][j] = max(maxw[u][j - 1], maxw[anc[u][j - 1]][j - 1]);
        for (unsigned i = 0; i < adj[u].size(); ++i)
        {
            int v = adj[u][i].v, w = adj[u][i].w;
            if (v != anc[u][0])
                dep[v] = dep[u] + 1, anc[v][0] = u, maxw[v][0] = w, dfs(v);
        }
    }
    int solve(int u, int v)
    {
        int res = 0;
        if (dep[u] < dep[v]) swap(u, v);
        for (int d = dep[u] - dep[v], j = 0; d ; d >>= 1, ++j)
            if (d & 1) res = max(res, maxw[u][j]), u = anc[u][j];
            //adjust u & v to the same depth
        if (u == v)    return res; //u & v meet now
        
        for (int j = 19; j >= 0; j--)
            if (anc[u][j] != anc[v][j]) // if anc[u][j] & anc[v][j] dont meet together, then jump u & v 
                res = max(res, maxw[u][j]),
                res = max(res, maxw[v][j]),
                u = anc[u][j], v = anc[v][j];
        //now u & v 's lca must be their parent now, in an easy word, it's anc[u][0] or anc[v][0]
        
        res = max(res, maxw[u][0]);
        res = max(res, maxw[v][0]);
        u = anc[u][0]; v = anc[v][0];
        return res;
    }
    int main()
    {
        cin >> n >> m >> Q;
        for (int i = 1, u, v, w; i <= m; i++)
            cin >> u >> v >> w, edg[i] = (edge){u, v, w};
        sort(edg + 1, edg + m + 1, cmp);
        for (int i = 1, u, v; i <= m; i++)
            if (merge(u = edg[i].u, v = edg[i].v))
                adj[u].push_back(edg[i]),
                adj[v].push_back((edge){v, u, edg[i].w});
                
        dfs(1);
        for (int u, v, i = 1; i <= Q; i++)
            cin >> u >> v, cout << solve(u, v) << endl;
    }
    
    /*Problem statement
    Given a graph G containing n nodes and m bidirectional edges.
    The weight of a path p0->p1->p2->...->pt is the maximum weight of edges on the path.
    There are Q queries given (u, v), output the minimum weight of paths from u to v.
    Guarantee between any nodes u & v , there is at least one path from u to v.
    */
  • 相关阅读:
    第四章、Vue组件
    第三章、Vue实例
    第二章、Vue指令
    Python练习实例012
    Python练习实例011
    Python练习实例010
    Python练习实例009
    Python练习实例008
    Python练习实例007
    Python练习实例006
  • 原文地址:https://www.cnblogs.com/lcezych/p/10803077.html
Copyright © 2011-2022 走看看