zoukankan      html  css  js  c++  java
  • 忧桑三角形,调了半天,真忧桑TAT

    忧桑三角形

    试题描述

    小J是一名文化课选手,他十分喜欢做题,尤其是裸题。有一棵树,树上每个点都有点权,现在有以下两个操作:

    1. 修改某个点的点权

    2. 查询点u和点v构成的简单路径上是否能选出三个点组成三角形

    输入

    第一行两个整数N,Q,代表点数和询问数。第二行N个整数表示点权。下面N-1行每行两个整数a,b代表a,b之间有一条边。下面Q行每行3个整数t,a,b:若t=0,询问(a,b);否则将点a的权值修改为b

    输出

    对于每个询问输出Y表示能构成三角形,输出N表示不能构成三角形

    输入示例

    5 5
    1 2 3 4 5
    1 2
    2 3
    3 4
    1 5
    0 1 3
    0 4 5
    1 1 4
    0 2 5
    0 2 3

    输出示例

    N
    Y
    Y
    N

    数据范围

    对于30%的数据有N, Q ≤ 1000
    对于100%的数据有N, Q ≤ 105,点权范围在32位整数范围内

    Orz wzj,方法超级机智。注意到“点权范围在32位整数范围内”这句话,这是个突破口,它告诉我们a和b距离很长的时候,一定能组成一个三角形,为什么呢?我们不妨写个程序算算。

    设经过的点权值序列为F,将F从大到小排序后,根据三角形三遍关系得到最好情况下Fi = Fi-1 + Fi-2(i > 2),于是可以写一个求F序列前50位的代码(为了迎合题目数据范围,我把F序列也定义为32位整型变量,下面用了一个滚动数组,F被压缩成了f1和f2):

    #define maxn 51
    
    int main() {
        int f1 = 1, f2 = 1; printf("%d %d ", f1, f2);
        for(int i = 1; i < maxn; i++) {
            f1 += f2; printf("%d ", f1);
            swap(f1, f2);
        }
        
        return 0;
    }

    (头文件自己加去吧……)

    运行一下,结果如下:

    1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465 14930352 24157817 39088169 63245986 102334155 165580141 267914296 433494437 701408733 1134903170 1836311903 -1323752223 512559680 -811192543 -298632863 -1109825406 -1408458269

    第47个数是负数,也就是说这个F序列长度超不过47
    所以算法来了:当a与b之间距离大于50时,直接输出Y,否则记录下路径所经过的节点权值然后排序处理。

    如何找a与b之间的距离呢?

    这里有一个思想上的易错点,你可能会想到DFS序判断祖先关系,当a是b的祖先时,说明在根节点同侧,所以用它们的深度相减,否则深度相加得到距离。乍一看思想很正确,我也因此调试半天……然而当他们的最近公共祖先(Lowest Common Ancestors,LCA)不是根节点时,第二种情况深度相加就不能得到正确距离了(想一想,为什么)。

    (你不知道什么叫最近公共祖先?对于有根树T的两个结点u、v,它们的最近公共祖先是一个结点x,满足x是u、v的祖先且x的深度尽可能大。)

    所以正确姿势是:只能好好写倍增算法求LCA了……

    略略讲一下LCA求法。显然直接dfs是O(n)级别,会超时的,所以我们可以设fa(u, x)表示节点u的第2x级祖先,不难得到递推式:fa(u, x) = fa(fa(u, x-1), x-1)

    你知道为什么要出现“第2x级祖先”吗?对于一条很长的链,我们可以把它看成一个序列,一个序列是可以分成若干个个数为2n(n∈Z+)的段的,就像一个数可以表示成二进制一样,所以一条链我们不必一个节点一个节点地走,而是可以一下跳2n个节点,而分后的序列数是log2(n)级别的,所以链上跳的步数也是log2(n)级别的。

    回答询问怎么做呢?对于给定的a和b,先把它们搞到同一个深度去,不妨设a深度大于b深度,所以我们就需要用上面所讲的“跳节点”的方式把节点a跳到节点b统一深度去。接下来判断:若a和b重合了,那么显然答案就是当前的b;否则还需要再进一步寻找,让a与b同时用“跳节点”的方式向上走,直到它们的父亲(即1级祖先,fa(u, 0))相同时(注意这里为什么是父亲相同,而不是自身相同,请读者思考),那么答案就是当前结点a或b的父节点了。

    怎么样?我们可以在O(logn)的时间内求出LCA了,是不是感觉棒棒哒,为自己鼓鼓掌!(- -|||)



    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #include <cmath>
    #include <algorithm>
    #include <stack>
    #include <vector>
    #include <queue>
    #include <cstdlib>
    using namespace std;
    
    int read() {
        int x = 0, f = 1; char c = getchar();
        while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
        while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
        return x * f;
    }
    
    #define maxn 200010
    #define maxlog 19
    #define maxm (maxn << 1)
    #define LL long long
    int n, q, val[maxn], m, head[maxn], next[maxm], to[maxm];
    
    void AddEdge(int a, int b) {
        to[++m] = b; next[m] = head[a]; head[a] = m;
        swap(a, b);
        to[++m] = b; next[m] = head[a]; head[a] = m;
        return ;
    }
    
    stack <int> S; // 人工栈需要
    bool vis[maxn];
    int dep[maxn], fa[maxn][maxlog], ToT;
    void build(int s) { // 注意数据可能卡链式图,20万的链式图必须得老老实实写人工栈了,否则爆内存别怪我
        S.push(s);      // 其实谁都不想写人工栈……
        while(!S.empty()) {
            int u = S.top();
            if(!vis[u]) {
                vis[u] = 1;
                for(int i = 1; i < maxlog; i++) if(fa[u][i-1]) fa[u][i] = fa[fa[u][i-1]][i-1]; // 这句是求LCA预处理的核心代码
                for(int e = head[u]; e; e = next[e]) if(to[e] != fa[u][0]) {
                    dep[to[e]] = dep[u] + 1; fa[to[e]][0] = u;
                    S.push(to[e]);
                }
            } else S.pop();
        }
        return ;
    }
    
    int query(int a, int b) { // 求LCA
        if(dep[a] < dep[b]) swap(a, b);
        for(int i = maxlog-1; i >= 0; i--) if(dep[a] - dep[b] >= (1 << i)) a = fa[a][i]; // 先提升到同一高度
        for(int i = maxlog-1; i >= 0; i--) if(fa[a][i] != fa[b][i]){ a = fa[a][i]; b = fa[b][i]; } // 再一起向上找祖先
        return a == b ? a : fa[a][0]; // a = b说明它们在同一条链上,不在一条链上时最后答案是他们的父亲(想一想,为什么)
    }
    
    int path[maxn], cnt;
    int main() {
        n = read(); q = read();
        for(int i = 1; i <= n; i++) val[i] = read();
        for(int i = 1; i < n; i++) AddEdge(read(), read());
        dep[1] = 1; build(n >> 1);
        while(q--) {
            int tp = read(), a = read(), b = read();
            if(tp) val[a] = b;
            else {
                int c = query(a, b), dis = dep[a] + dep[b] - (dep[c] << 1) + 1;
                if(dis > 50){ puts("Y"); continue; }
                if(dis < 3){ puts("N"); continue; }
                cnt = 0; // 记录a到b简单路径上各边权值
                while(a != c){ path[++cnt] = val[a]; a = fa[a][0]; }
                while(b != c){ path[++cnt] = val[b]; b = fa[b][0]; }
                path[++cnt] = val[c];
                sort(path + 1, path + cnt + 1);
                bool ok = 0;
                for(int i = 1; i < cnt - 1; i++) if((LL)path[i] + path[i+1] > (LL)path[i+2]) {
                    ok = 1; break;
                }
                puts(ok ? "Y" : "N");
            }
        }
        
        return 0;
    }
  • 相关阅读:
    ES6-->ECMAScript 6.0 新增方法,一些基本语法
    初识 Nodejs (了解Nodejs)
    Vue框架初识
    python语法入门之流程控制
    python中基本运算符
    格式化输出
    基本数据类型
    变量,解释器,垃圾回收机制,小整数池总结
    编程语言发展史
    计算机基础
  • 原文地址:https://www.cnblogs.com/xiao-ju-ruo-xjr/p/4945899.html
Copyright © 2011-2022 走看看