zoukankan      html  css  js  c++  java
  • LOJ#2718. 「NOI2018」归程 (kruskal重构树)

    LOJ#2718. 「NOI2018」归程 (kruskal重构树)

    参考博客

    题意: 在无向图中,每条边的属性不仅有距离,还有海拔。有Q次询问,每次给出两个值v,p表示一个人从v点出发回到家(家在1点),且世界的海平面海拔是P。也就是小于p的路都在水中。现在这个人可以开车走一段路直到遇到低于海平面的路,他就丢弃车开始步行回家,问给定v,p条件下这个人回家最少需步行的距离。

    题解:

    这道题很不错。首先我发现不考虑海拔,每个点x回家的最小距离是从1到x的最短路。故可以求Dijkstra(1),然后把海拔低于p的路理解成断路,图分成许多联通块。O(n)求与v在一个联通图中的所有点 x 的 dis[x]的最小值就是答案。这个方法是正确的,但复杂度O(Q*n) 爆炸了。。

    所以我开始想方法在合适的复杂度内求这个联通块内的dis[x]最小值,最后没想出来,看题解学习了kurskal重构树这一“神奇”操作。

    利用kruskal重构树的性质,从每个叶子点到根权值是单调的,如果我们按照海拔从大到小建树,那么两个点的lca点的权值val[lca]就是这两个点路径上的最低海拔的最大值。如果对一点 u 有 val[u] > p,那么这个u 的子树里的叶子中任意两个点之间的路径的边都大于p。也就是这个 u 的子树叶子上的点 在原图中把小于等于p的边断掉后的图中和v在一个联通图中。

    所以如果我们在建树时让每个点u的d[u]表示子树d[]的最小值 。那么当我们找到这个u 点时就可以O(1)输出答案了。

    如何找这个点u呢? 我们可以利用求lca得f[][]数组, 如下程序

    for(int j = 20; j >= 0; -- j)
        if(f[v][j] && val[f[v][j]] > p) 
            v = f[v][j];
    

    代码:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #include<map>
    #include<queue>
    #include<vector>
    #include<string>
    #include<bitset>
    #include<fstream>
    using namespace std;
    #define rep(i, a, n) for(int i = a; i <= n; ++ i);
    #define per(i, a, n) for(int i = n; i >= a; -- i);
    typedef long long ll;
    typedef pair<ll,int> PII;
    const int N = 2e6 + 105;
    const int mod = 1e9 + 7;
    const double Pi = acos(- 1.0);
    const ll INF = 1e18; 
    const int G = 3, Gi = 332748118;
    ll qpow(ll a, ll b) { ll res = 1; while(b){ if(b & 1) res = (res * a) % mod; a = (a * a) % mod; b >>= 1;} return res; }
    ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }
    // bool cmp(int a, int b){return a > b;}
    //
    
    int n, m, t;
    int head[N], cnt = 0, tot;
    int to[N << 1], nxt[N << 1]; ll c[N << 1];
    int root[N << 1];
    int f[N][30];
    int dep[N << 1];
    queue<int> q;
    ll val[N << 1];
    
    void add(int u, int v, ll w){
        to[cnt] = v, c[cnt] = w, nxt[cnt] = head[u], head[u] = cnt ++;
        to[cnt] = u, c[cnt] = w, nxt[cnt] = head[v], head[v] = cnt ++;
    }
    int Find(int x){
        return x == root[x] ? x : root[x] = Find(root[x]);
    }
    
    struct node{
        int u, v; ll w, h;
    }e[N << 1];
    
    bool cmp(node a, node b){
        return a.h > b.h;
    }
    
    //Dijkstra板子
    ll d[N << 1];
    void Dijkstra(int st)
    {
        priority_queue<PII, vector<PII>, greater<PII> > q;
        for(int i = 1; i <= n * 2 + 1; ++ i) d[i] = INF;
        d[st] = 0;
        q.push(PII(0, st));
        while(!q.empty()){
            int u = q.top().second; ll w1 = q.top().first; q.pop();
            if(w1 != d[u]) continue;
            for(int i = head[u]; i != -1; i = nxt[i]){
                int v = to[i]; ll w2 = c[i];
                if(d[u] + w2 < d[v]){
                    d[v] = d[u] + w2;
                    q.push(PII(d[v], v));
                }
            }
        }
    }
    
    void Build_Kruskal(){	//建重构树
        sort(e + 1, e + m + 1, cmp);
        int Count_Edge = 0;
        for(int i = 1; i <= m; ++ i){
            int x = e[i].u, y = e[i].v; ll z = e[i].h;
            int tx = Find(x), ty = Find(y);
            if(tx == ty) continue;
            root[tx] = root[ty] = ++ tot;
            val[tot] = z;
            d[tot] = min(d[ty], d[tx]);
            f[tx][0] = f[ty][0] = tot;
            if( (++ Count_Edge) == n - 1) break;
        }
    }
    
    int main()
    {
        freopen("return.in","r",stdin);	//注意加读写文件重定向。
        freopen("return.out","w",stdout);
        int T;
        scanf("%d",&T);
        while(T --){
            scanf("%d%d",&n,&m);
            cnt = 0; tot = n;
            for(int i = 0; i <= n * 2 + 10; ++ i){
                root[i] = i; head[i] = -1;
            }
            memset(f, 0, sizeof(f));
            for(int i = 1; i <= m; ++ i){
                scanf("%d%d%lld%lld", &e[i].u, &e[i].v, &e[i].w, &e[i].h);
                add(e[i].u, e[i].v, e[i].w);
            }
            Dijkstra(1);
            Build_Kruskal();
            
            for (int j=1; j<=20; ++j)
                for (int i=1; i<=tot; ++i)
                    f[i][j] = f[f[i][j-1]][j-1];
            
            int Q, K, S; int v; ll p;
            scanf("%lld%lld%lld",&Q,&K,&S);
            ll ans = 0;
            while(Q --){
                scanf("%d%lld",&v, &p);
                v = (v + K * ans - 1) % n + 1;
                p = (p + K * ans) % (S + 1);
                
                for(int j = 20; j >= 0; -- j)	
                    if(f[v][j] && val[f[v][j]] > p) v = f[v][j];
                ans = d[v];
                printf("%lld
    ",ans);
            }
        }
        return 0;
    }
    
    
    
  • 相关阅读:
    C,LINUX,数据结构部分
    LINUX应用开发工程师职位(含答案)
    INT32 System_UserKeyFilter(NVTEVT evt, UINT32 paramNum, UINT32 *paramArray)
    屏幕调试
    1.ARM嵌入式体系结构与接口技术(Cortex-A8版)
    NT9666X调试log
    DemoKit编译过程错误
    selenium 代理设置
    pandas 轮询dataframe
    Spring 定时任务
  • 原文地址:https://www.cnblogs.com/A-sc/p/13742661.html
Copyright © 2011-2022 走看看