首页 > 其他分享 >「模拟赛」CSP-S 模拟 11(T2 超详细)

「模拟赛」CSP-S 模拟 11(T2 超详细)

时间:2024-10-14 20:12:28浏览次数:1  
标签:11 ned T2 son AVL 深度 儿子 节点 模拟

比赛链接

A.玩水 (water)

签到。发现如果要找两条路径的话,能找到的充要条件是存在一个点的上方和左方的字母相同。(即使两条走过的点截然不同的路径也符合,这时终点会成为这个点)。

即存在一个位置 \((i,j)\) 使得 \(s_{i-1,j}=s_{i,j-1}\),我们称位置 \((i,j)\) 是好位置

扩展到三条路发现,存在上面的两个好位置就可以了,但对这两个位置有要求:

  • 两点相邻,即以下情形:

或者 同一列上相邻

  • 两个好位置 \((i_1,j_1),(i_2,j_2)\) 满足 \(i_2>i_1,j_2>j_1\)

B. AVL 树

贪心,思路其实很简单,只不过不好实现。

发现尽量留下小的节点比较优,于是中序遍历,每到一个节点则求出若留下它则至少要留多少个点(设这个数量为 \(x\)),比较 \(x\) 与 \(k\),若 \(x<k\) 则这个点可以留下来,标记上。

考虑具体如何查询一个点留下来至少要留的点的个数

  • 对于查询:

    发现若一颗 AVL 树的高度若是确定的,则它最少包含的节点数量可求:

    可以预处理出来 \(f[i]\),表示深度为 i 的AVL树,节点数至少多大。有递推式 \(f[i]=f[i−1]+f[i−2]+1\),可以看作一边深度为 i−1,一边深度为 i−2,在上面接上了根节点。

    那么查询求出一个点留下来,一直回溯它的祖先节点直到根,对于该节点是左儿子的情况计算出保留这个节点至少需要右子树中有多少个节点,即可转化成求右子树的最小高度。

    我们维护每个点的 深度 \(dep\)、子树内最大深度 \(dmax\),dfs 预处理这两个信息即可。

    还要维护每个点为根的 子树中已选点的最大深度 \(had\),和若留下该点至少需要的深度 \(ned\)

    这样询问所需点的个数就好说了,在询问点不断跳父亲回溯的过程中,当跳到的点是左儿子时,求出保留这个点,它父节点的右儿子需要达到的最小高度。(因为当前左子树有一定深度,为了满足 AVL 树的性质,要使右子树的深度与其差值小于等于 1)

具体看代码,那么现在我们还需要实时更新这些信息。

  • 考虑更新:

    • 每个点被选后都要不断回溯祖先节点至根节点来更新它所有祖先节点的已选子树的最大深度

    • 并且回溯过程中,该点为左儿子时需要更新父节点的右儿子留下来需要的最大深度,(为了保证满足 AVL 树的要求,即两儿子子树的高度差值不超过 1)

      而该点为右儿子时则不用管父节点的左儿子,因为左儿子一定被我们优先考虑过是否可留下,右儿子在左儿子之后遍历到,不对左儿子有影响。

由于 AVL 树的性质,高度约为 \(\log n\),所以整体复杂度为 \(O(n\log n)\)。

具体还有很详细的代码注释:

code
#include<bits/stdc++.h>
#define mp make_pair
#define Type int
#define qr(x) x=read()
typedef __int128 INT;
typedef long long ll;
using namespace std;

inline Type read(){
    char c=getchar(); Type x=0, f=1;
    while(!isdigit(c)) (c=='-'?f=-1:f=1), c=getchar();
    while(isdigit(c)) x=(x<<1)+(x<<3)+(c^48), c=getchar();
    return x*f;
}

const int N = 5e5 + 5; 

int n, k, root, whi[N];
int son[N][2], fa[N], f[N];

int dep[N], dmax[N];
inline void dfs(int x, int p){ dfs 预处理
    dmax[x] = dep[x] = dep[p] + 1;
    for(int i=0; i<2; i++){
        int y = son[x][i];
        if(!y) continue;
        dfs(y, x);
        dmax[x] = max(dmax[x], dmax[y]);
    }
}

int ned[N], had[N], vis[N];
inline int query(int u){
    int y = u, x = fa[u], res = 0;
    while(x != -1){
        if(!whi[y]) res += f[max({had[x]-1, dep[u]-1, ned[son[x][1]]})-dep[x]];// 跳到点为左儿子时,加上右儿子的贡献
        y = x, x = fa[x];
    }
    return res;
}

inline void update(int u){ //选了一个点,更新它对祖先节点的影响
    had[u] = max(had[u], dep[u]); 
    int y = u, x = fa[u];
    while(x != -1){
        had[x] = max(had[x], dep[u]);//更新子树内已选的点的最大深度
        if(!whi[y] and son[x][1]) ned[son[x][1]] = max(ned[son[x][1]], had[x] - 1); //该点是左儿子,更新若选右儿子需要的最大深度
        y = x, x = fa[x];
    }
}

inline void DFS(int x){
    if(query(x) < k){
        vis[x] = true;
        k--; update(x);
    }
    if(son[x][0] and dmax[son[x][0]] >= ned[x]){ //左子树的最大深度达得到父节点需要的就选择左子树
        ned[son[x][0]] = max(ned[son[x][0]], ned[x]);
        if(son[x][1]) ned[son[x][1]] = max(ned[son[x][1]], ned[x] - 1);
    }
    else if(son[x][1]){ //否则选择右子树
        ned[son[x][1]] = max(ned[son[x][1]], ned[x]);
        if(son[x][0]) ned[son[x][0]] = max(ned[son[x][0]], ned[x] - 1);
    }

    for(int i=0; i<2; i++){
        int y = son[x][i];
        if(!y) continue;
        DFS(y);
    }
}

signed main(){ //avl
    freopen("avl.in", "r", stdin), freopen("avl.out", "w", stdout);

    qr(n), qr(k);
    for(int i=1; i<=n; i++){
        qr(fa[i]);
        if(fa[i] == -1){root = i;continue;}
        whi[i] = (i < fa[i] ? 0 : 1); //判断该点是其父节点的左子树还是右子树
        son[fa[i]][whi[i]] = i;
    }

    if(k == 1){
        for(int i=1; i<=n; i++)
            cout<<(i == root ? 1 : 0);
        return 0;
    }

    dfs(root, 0);

    f[1] = 1;
    for(int i=2; i<=30; i++) f[i] = f[i-1] + f[i-2] + 1; //递推求深度为 i 的 AVL 树的最少节点数

    DFS(root);        

    for(int i=1; i<=n; i++)
        cout<<vis[i];
    cout<<'\n';


    return 0;
}

标签:11,ned,T2,son,AVL,深度,儿子,节点,模拟
From: https://www.cnblogs.com/YuenYouth/p/18464923

相关文章

  • 2024.10.13 模拟赛
    2024.10.13模拟赛T1「KDOI-10」商店砍价赛时直接口胡出一个错误的贪心。一开始的想法是按照\(v[i]\)排序,然后假设输入的长度为\(n\)位。那么对于排序后\(n-5\)位直接选择操作一。对于剩下的,直接bdfs所有情况选答案,复杂度\(O(n\logn)\)。貌似可行,结果随便一个数据......
  • [47] (CSP 集训) CSP-S 模拟 11
    因为有人想看T3\(nk^2\)所以先发一下A.玩水注意到只有在形如下面这样的地方才会发生分叉?aa?所以\(f_{i,j}\)表示从\((1,1)\)到\((i,j)\)的矩阵中的最大路径方案数,考虑转移普通的转移是\(f_{i,j}=\max(f_{i,j-1},f_{i-1,j})\)如果\(a_{i-1,j}=a_{i,j-1}\),则有......
  • 2024/10/13 模拟赛总结
    人机体检,\(0+0+0+0=0\),打代码源去了#A.一般图最小匹配下次看到这种范围一定要想到dp啊,令\(dp_{i,j}\)为前\(i\)个元素选了\(j\)对点的最小代价由于边权是绝对值,可以对原数组排一遍序,选取的两个点就一定在排序后数组的相邻节点那么就可以得出式子:\(dp_{i,j}=\min\{dp_......
  • 【2024潇湘夜雨】WIN10_Ent-G_22H2.19045.5011软件选装纯净特别版10.14
    【系统简介】=============================================================1.本次更新母盘来自WIN10_Ent-G_22H2.19045.5011.进桌面后稍等片刻,等待后续部分优化完成。2.全程离线精简、无人值守调用优化处理制作。部分优化适配系统可能要重启几次,即使显示适配失败也不要在意,可能......
  • 题解:P11132 【MX-X5-T4】「GFOI Round 1」epitaxy
    ProblemLink【MX-X5-T4】「GFOIRound1」epitaxy题目描述给你两个正整数\(n,m\)。定义一个\(1\simn\)的排列\(p\)的价值为所有的\(n-m+1\)个长度为\(m\)的连续子串内最大值的最大公因数。(规定单个数的最大公因数为其自身。)请你求出一个在所有\(1\simn\)......
  • 题解:P11145 Strange Homura Game
    ProblemLinkStrangeHomuraGame题意让你猜测一个数\(n\),你只能输出两次,每次输出一个数\(x\),返回\(x\bmodn\)。Solution令输入的数为\(A,B\),输出的数为\(a,b\),答案为\(n\)。一开始想的是CRT,但只能询问\(2\)次。发现输入的值是经过\(\bmodn\)的,已知\((A-a)......
  • 题解:P11063 【MX-X4-T3】「Jason-1」数对变换
    ProblemLink【MX-X4-T3】「Jason-1」数对变换题外话场上把贪心注重在一个奇怪地方了,导致交的时候已经有\(9\)个人\(\textcolor{green}{AC}\)了(哭)。题意简述对于数对\((x,y)\),你可以执行以下两种变换:类型1:取\(1\lek\lex\),令\((x,y)\gets(\lfloor\frac{x}{k}......
  • 省选模拟赛
    认为只有k个位置有值的序列差分之后会形成k个颜色段,建议送回普及组重学差分与前缀和A考虑把硬币序列异或差分,操作变成选两个距离为\(a_i\)的位置翻转,差分完序列上只有\(2k\)个\(1\),对其中任意两个\(1\)预处理出把它们同时变为\(0\)的最小操作数,状压BFS即可。B......
  • csp-s模拟11
    csp-s模拟11\(T1\)T2203.玩水(water)\(100pts\)定义一个点\((i,j)\)是分点当且仅当\(s_{i,j+1}=s_{i+1,j}\),而一个点\((i,j)\)是合点当且仅当\((i-1,j-1)\)是分点。先考虑若只有两个人时,只要存在一个分点就一定有解。扩展到三个人时,若存在一个合点可以通过......
  • CSP模拟赛 #37
    A题意:给定\(n,a_{1\simn},b_{1\simn}\),两个点\(i,j\)之间有连边当且仅当\(a_i-a_j\lei-j\leb_i-b_j\)或\(a_j-a_i\lej-i\leb_j-b_i\),求图中连通块数量。\(1\len\le10^6\)考虑条件\(a_i-a_j\lei-j\leb_i-b_j\)相当于\(a_i-i\lea_j......