首页 > 其他分享 >CF1899 G Unusual Entertainment 题解

CF1899 G Unusual Entertainment 题解

时间:2023-11-20 16:36:25浏览次数:32  
标签:Unusual CF1899 题解 ++ int tin vector tout --

Link

CF1899 G Unusual Entertainment

Question

给出一个排列 \(p_i\) 和一棵树,给出 \(Q\) 组询问,每组询问 \([L,R,x]\) 表示求 \(p_L \sim p_R\) 上是否存在 \(p_i\) 在 \(x\) 的字数上。

Solution

这道题确实是一个好题。

我们先考虑一个问题,怎么样才能判断子树,我们给书上的每个节点建立两个时间戳 \(tin[x],tou[x]\) ,对于两个节点 \(u,v\) 如果 \(u\) 在 \(v\) 的子树上,满足 \(tin[v] \le tin[u] \le tou[v]\)。

那么我们设 \(a_i=tin[p_i]\) ,那么这个问题就转化为在 \(a_L \sim a_R\) 上,是否存在一个 \(a_i\) 在 \([tin_x,tou_x]\) 上。

接下来考虑如何解决这个转化以后的问题,观察到 \(p_i\) 是无序的,如果 \(p_i\) 是有序的,那么这个问题就很简单了。

假设 p 数组为 2,5,7,10 ,\(tin_x\) 为 9 \(tou_x\) 为 11,那么使用差分的思想,在 p 数组内二分 \(tin_x\) 和 \(tou_x\) 的位置,如果位置不相同,说明 p 数组中有一个数 \(p_i\) 满足 \(tin_x \le p_i \le tout_x\),并且,这种方法能让我们得出有几个数满足这个条件。

那么如何让 p 数组有序呢,可以使用归并树,类似于线段树的思想,只不过每个节点都记录了对应排序好的值,例如,当前节点的区间为 \([l,r]\),那么就可以用一个 vector<int> 来记录 \([l,r]\) 排序好的值,在向上更新的时候归并排序就好了。

image.png

类似于线段树的思想,每一个询问区间 \([L,R]\) 最多被分成 \(log_2N\) 个小区间,然后各个小区间里面二分求解,所以总的时间复杂度是 \(O(Qlog^2N)\)。

Code

#include <bits/stdc++.h>
 
using namespace std;
 
#define sz(x) (int)x.size()
#define all(x) x.begin(), x.end()
 
struct SegmentTree {
    int n;
    vector<vector<int>> tree;
 
    void build(vector<int> &a, int x, int l, int r) {
        if (l + 1 == r) {
            tree[x] = {a[l]};
            return;
        }
 
        int m = (l + r) / 2;
        build(a, 2 * x + 1, l, m);
        build(a, 2 * x + 2, m, r);
        merge(all(tree[2 * x + 1]), all(tree[2 * x + 2]), back_inserter(tree[x]));
    }
 
    SegmentTree(vector<int>& a) : n(a.size()) {
        int SIZE = 1 << (__lg(n) + bool(__builtin_popcount(n) - 1));
        tree.resize(2 * SIZE - 1);
        build(a, 0, 0, n);
    }
 
    int count(int lq, int rq, int mn, int mx, int x, int l, int r) {
        if (rq <= l || r <= lq) return 0;
        if (lq <= l && r <= rq) return lower_bound(all(tree[x]), mx) - lower_bound(all(tree[x]), mn);
 
        int m = (l + r) / 2;
        int a = count(lq, rq, mn, mx, 2 * x + 1, l, m);
        int b = count(lq, rq, mn, mx, 2 * x + 2, m, r);
        return a + b;
    }
 
    int count(int lq, int rq, int mn, int mx) {
        return count(lq, rq, mn, mx, 0, 0, n);
    }
};
 
vector<vector<int>> g;
 
vector<int> tin, tout;
int timer;
void dfs(int v, int p) {
    tin[v] = timer++;
    for (auto u : g[v]) {
        if (u != p) {
            dfs(u, v);
        }
    }
    tout[v] = timer;
}
 
void solve() {
    int n, q;
    cin >> n >> q;
    
    g.assign(n, vector<int>());
    for (int i = 0; i < n - 1; i++) {
        int u, v;
        cin >> u >> v;
        u--; v--;
        g[u].push_back(v);
        g[v].push_back(u);
    }
 
    timer = 0;
    tin.resize(n);
    tout.resize(n);
    dfs(0, -1);
    
    vector<int> p(n);
    for (int i = 0; i < n; i++) cin >> p[i];
 
    vector<int> a(n);
    for (int i = 0; i < n; i++) a[i] = tin[p[i] - 1];
    SegmentTree ST(a);
 
    for (int i = 0; i < q; i++) {
        int l, r, x;
        cin >> l >> r >> x;
        l--; x--;
        if (ST.count(l, r, tin[x], tout[x])) {
            cout << "YES\n";
        } else {
            cout << "NO\n";
        }
    }
}
 
int main() {
    int tests;
    cin >> tests;
    while (tests--) {
        solve();
        if(tests > 0) cout << "\n";
    }
    return 0;
}

标签:Unusual,CF1899,题解,++,int,tin,vector,tout,--
From: https://www.cnblogs.com/martian148/p/17844256.html

相关文章

  • Windows端口占用问题解决
    查询被占用的端口进程8080为端口号netstat-ano|findstr8080杀掉线程14816为进程号taskkill/f/t/im14816......
  • noip2023 题解(民间数据)
    P9868[NOIP2023]词典(民间)直接把每个串\(w_i\)都从大到小/从小到大排一下,记作\(a_i,b_i\)。如果\(b_i\)小于除了\(i\)之外的所有\(a_i\),说明可以,否则不行。求一个前后缀最大值即可。复杂度\(\mathcal{O}(26n+nm)\)。点击查看代码#include<bits/stdc++.h>usingname......
  • 【洛谷 P1125】[NOIP2008 提高组] 笨小猴 题解(字符串+映射+集合)
    [NOIP2008提高组]笨小猴题目描述笨小猴的词汇量很小,所以每次做英语选择题的时候都很头疼。但是他找到了一种方法,经试验证明,用这种方法去选择选项的时候选对的几率非常大!这种方法的具体描述如下:假设是单词中出现次数最多的字母的出现次数,是单词中出现次数最少的字母的出现次数,......
  • [ABC326C] Peak 题解
    题目链接题目思路这个问题要求找到一个半开区间,使得在这个区间内包含尽可能多的礼物。首先,我们需要将输入的礼物坐标按照从小到大的顺序进行排序。然后,我们可以使用双指针的方法来寻找最佳的区间。代码以下是代码解释:#include<bits/stdc++.h>usingnamespacestd;constint......
  • [ABC328C] Consecutive 题解
    HelloWorld链接这道题是一个很明显的前缀和,我们把$sum_i$表示为前$i$个字符有多少个有重复,查询的时候就用$sum_{r-1}-sum_{l-1}$就行了。代码#include<bits/stdc++.h>usingnamespacestd;strings;intsum[300010];intmain(){ intn,q; cin>>n>>q>>s; for(in......
  • CF222A Shooshuns and Sequence 题解
    分析这题是一个很水的题,就是对一个序列有$2$种操作方法,一种是对第$K$个数以前的数的第一个进行删除,另一个则是在整个序列后添加这第$K$个数,使得整个序列为同一个数字,显然,后者是无效操作,则只需要判断第$K$个数以后有无与第$K$个不同的数,有则无解,反之有解。若有解,然后再......
  • CF601B Lipshitz Sequence 题解
    给你一个序列\(v_{1\dotsn}\),定义\(f(v)\)为\(v\)中斜率最大值(\(\lvertv\rvert=1\)则\(f(v)=0\)),有\(q\)组询问,每次给定\(1\lel\ltr\len\),求\(a_{l\dotsr}\)的每个子区间的\(f\)之和。一个关键的性质是,最大的斜率只在相邻数间取到。有了这个性质,这题......
  • NEFU OJ Problem 1489 青蛙赶路 题解【动态规划DP】
    Problem:GTimeLimit:2000msMemoryLimit:65535KDescription有一只青蛙,每秒选择走1米或跳m米,青蛙体力不足,所以不能连续两秒都在跳。青蛙将移动到[l,r]之间,它想知道有多少种不同的方式来实现其目标。两种方式是不同的,当且仅当它们移动不同的米或花费不同的秒,或者是在一秒......
  • NEFU OJ Problem1485 贪吃蛇大作战 题解
    Problem:FTimeLimit:1000msMemoryLimit:65535K题目Description贪吃蛇大家一定都玩过吧,现在宋哥也要玩这个游戏,最初的时候贪吃蛇从屏幕的左下角出发,但是有一个非常不幸的事情,就是宋哥的游戏机的左键和下键坏掉了,这意味着什么?没错!他只能操控他的蛇向右或向上走了,假设屏幕......
  • ICPC2023深圳部分题解(A,D,E,F,G,K,L)
    目录正题A一道好题题目大意解题思路D机器人兄弟题目大意解题思路E二合一题目大意解题思路F见面礼题目大意解题思路G相似基因序列问题题目大意解题思路K四国军棋题目大意解题思路LMary有颗有根树题目大意解题思路正题好像还没上gym所以放不了题目链接,深圳这场的题目我觉......