ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

[题解]CF1527D MEX Tree

2021-07-26 14:32:02  阅读:194  来源: 互联网

标签:siz return 题解 路径 Tree else lp LCA MEX


题目链接

#1.0 基本思路

首先需要理解这个 "mex" 的含义,我们需要找出所有的路径使得 \(i\) 是编号最小的未出现的点,那么也就意味着 \([0,i-1]\) 应当全部包含在我们要找的路径中。那么我们便可以尝试维护这样的路径:路径两端 \(x, y\in[0,i-1]\),\([0,i-1]\) 包含在路径经过的点集中,然后计算有多少条路经可以不经过 \(i\)(询问)。最后再尝试将 \(i\)​ 加入当前维护的路径(修改)。

不妨先将整棵树的根节点设为 \(0\)。

#2.0 分类讨论

下文中会反复运用 LCA 来进行判断,这里采用 st 表 \(O(n\log n)\) 预处理,\(O(1)\)​ 询问的方法。​

  • siz[i] 表示以 \(i\)​ 为根的子树大小。
  • sp 为非零端点所在 \(0\) 的对应儿子的子树大小

本题涉及到的情况比较多,这里会一一讨论。

#2.1 询问

先来看询问的部分,整体分为 \(3\)​ 种情况:

  • \(i\) 在当前路径中
  • \(i\) 是当前路径端点的子孙
  • \(i\) 不属于上面的任意一种情况

这里再拎出来两种比较特殊的情况:\(mex=0\) 和 \(mex=1.\)

当 \(mex=0\)​ 时,每次选择的路径应当是都在同一 \(0\)​ 的子树内的两个点为端点;当 \(mex=1\)​​ 时,可行的路径数为在去掉 \(1\)​ 的子树后的点集中任意选取两个点产生的路径条数,减去不经过 \(0\) 的路径数量(即在仅在 \(0\)​ 的子节点的子树内的路径),需要特判是 \(1\) 的祖先的子树的情况,采用 LCA 判断。计算代码如下:

inline void pre_ans() {
    ans[1] = (siz[0] - siz[1]) * (siz[0] - siz[1] - 1) / 2;
    for (int i = head[0]; i; i = e[i].nxt) {
        ans[0] += (1ll * siz[e[i].v] * (siz[e[i].v] - 1)) / 2;
        int lca = LCA(1, e[i].v);
        if (lca == e[i].v) ans[1] -= ((siz[e[i].v] - siz[1])
                                    * (siz[e[i].v] - siz[1] - 1) / 2);
        else ans[1] -= siz[e[i].v] * (siz[e[i].v] - 1) / 2;
    }
}

解决上面的两种特殊情况后,我们再来看将所有情况分为两种情况:

  • 路径一端是零
  • 路径两端都不是零

先来看路径一端是零(两端分别为 \(x,0\))的情况:

  • \(i\)​ 在当前路径中

    \(LCA(x,i)=i\),这种情况答案一定为 \(0\);

  • \(i\) 是当前路径端点的子孙

    1. \(i\)​ 是不为零的一端的端点的子孙。那么答案应当是 (siz[x] - siz[i]) * (siz[0] - sp)。这种情况的判断条件 \(LCA(x,i)=x\);
    2. \(i\)​ 是不与非零端点所在 \(0\)​ 的对应儿子的子树中。那么答案是 (siz[0] - sp - siz[i]) * siz[x]。这种情况的判断条件 \(LCA(x, i)=0\)​;
  • \(i\) 不属于上面的任意一种情况

    那么也就意味着 \(i\) 是当前路径上的一个分叉,答案即为 (siz[0] - sp) * siz[x]

再来看路径两端 \(x,y\) 都不是零的情况:

  • \(i\) 在当前路径中

    \(LCA(x,i)=i\) 或 \(LCA(y,i)=i\)​,那么答案为零;

  • \(i\) 是当前路径端点的子孙

    答案为对应端点子树大小减去 siz[i] 再乘上另一端点子树大小;

  • \(i\)​ 不属于上面的任意一种情况

    答案为 siz[x] * siz[y]

以上,便是查询操作的所有内容。以下是代码:

inline ll GetAns(int k){
    int lca1 = LCA(k, lp), lca2 = LCA(k, rp);
    ll res1, res2;
    if (rp) { //这里在插入时会保证为 0 的一端一定是 rp
        res1 = siz[lp]; res2 = siz[rp];
        if (lca1 == k || lca2 == k) return 0;
        /*i 在当前路径上*/
        else if (lca1 == lp) res1 -= siz[k];
        else if (lca2 == rp) res2 -= siz[k];
        /*两种不同的情况*/
    } else {
        res1 = siz[lp]; res2 = siz[rp] - sp;
        if (lca1 == k || lca2 == k) return 0;
        else if (lca1 == lp) res1 -= siz[k];
        else if (lca1 == 0) res2 -= siz[k];
    }
    return res1 * res2;
}

#2.2 修改

现在来尝试将 \(i\) 加入当前所维护的路径。

仍旧分为两种大情况:

  • 至少一端为 \(0\);
  • 两端都不为 \(0\);

若两端都不为 \(0\),那么有以下三种情况:

  • 在路径上。即 \(LCA(x, i)=i\) 或 \(LCA(y,i)=i\),那么说明 \(i\) 已经在路径上。
  • 是其中一端的儿子,即 \(LCA(x,i)=x\) 或 \(LCA(y,i)=y\),将对应的端点设置为 \(i\) 即可;
  • 不属于上面的两种情况,即是当前路径的分叉,那么显然不可能将 \([1,i]\) 通过一条路径相连,那么后面的所有答案数都应当是 \(0\)​。

若存在至少一端 \(y=0\),有以下几种情况:

  • 另一端也是 \(0\),那么令其中一端变为 \(i\);
  • \(LCA(x,i)=i\),已经在路径上;
  • \(LCA(x,i)=i\),更新 \(x=i\)​;
  • 不属于上面的情况但 \(LCA(x,i)\ne0\),那么意味着分叉了,插入失败;
  • \(x\ne0\) 且 \(LCA(x,i)=0\),那么更新 \(y=0\);
inline bool add(int x) {
    int lf = LCA(lp, x), rf = LCA(rp, x);
    if (rp) {
        if (lf == lp) {lp = x; return true;}
        else if (lf == x) return true;
        if (rf == rp) {rp = x; return true;}
        else if (rf == x) return true;
    } else {
        if (lf && (lf != lp && lf != x)) return false;
        else if (lf == lp) {lp = x; return true;}
        else if (lf == x) return true;
        else if (lf) return false;
        else {rp = x; return true;}
    }
    return false;
}

#3.0 完整代码

有许多数组可以不 memset() 就不 memset();比如本题中的 dep[]siz[]

const int N = 600010;
const int INF = 0x3fffffff;

struct Edge {
    int u, v;
    int nxt;
};
Edge e[N << 1];

int n, head[N], cnt = 1, st[N << 1], scnt, lp, rp;
int mn[N << 1][30], dep[N], w[N], rt, t, lg2[N], f1, f2;
ll ans[N], sp, siz[N];

inline void clear() { //清空
    cnt = 1, scnt = lp = rp = 0;
    mset(head, 0); dep[0] = 0;
}

inline void add(const int &u, const int &v) {
    e[cnt].u = u, e[cnt].v = v;
    e[cnt].nxt = head[u], head[u] = cnt ++;
}

inline int LCA(int a, int b) { //O(1) 获得 LCA
    int l = w[a], r = w[b];
    if (r < l) swap(l, r);
    int k = lg2[r - l + 1];
    if (dep[mn[l][k]] < dep[mn[r - (1 << k) + 1][k]])
        return mn[l][k];
    else return mn[r - (1 << k) + 1][k];
}

void dfs(int x, int fa) { //计算 siz[],dep[] 等数组
    st[++ scnt] = x, w[x] = scnt;
    siz[x] = 1, dep[x] = dep[fa] + 1;
    for (int i = head[x]; i; i = e[i].nxt) {
        if (e[i].v == fa) continue;
        dfs(e[i].v, x); st[++ scnt] = x;
        siz[x] += siz[e[i].v];
    }
}

inline void get_st() { //计算 st 表,用于 O(1) LCA
    lg2[1] = 0;
    for (int i = 2; i <= scnt; ++ i) lg2[i] = lg2[i >> 1] + 1;
    for (int i = 1; i <= scnt; ++ i) mn[i][0] = st[i];
    for (int j = 1; j <= lg2[scnt]; ++ j)
      for (int i = 1; i + (1 << j) - 1 <= scnt; ++ i)
        if (dep[mn[i][j - 1]] < dep[mn[i + (1 << (j - 1))][j - 1]])
          mn[i][j] = mn[i][j - 1];
        else mn[i][j] = mn[i + (1 << (j - 1))][j - 1];
}

inline void pre_ans() { //预处理 0 和 1 的答案
    ans[1] = (siz[0] - siz[1]) * (siz[0] - siz[1] - 1) / 2;
    for (int i = head[0]; i; i = e[i].nxt) {
        ans[0] += (1ll * siz[e[i].v] * (siz[e[i].v] - 1)) / 2;
        int lca = LCA(1, e[i].v);
        if (lca == e[i].v) ans[1] -= ((siz[e[i].v] - siz[1])
                                    * (siz[e[i].v] - siz[1] - 1) / 2);
        else ans[1] -= siz[e[i].v] * (siz[e[i].v] - 1) / 2;
    }
}

inline bool add(int x) { //尝试将点加入路径
    int lf = LCA(lp, x), rf = LCA(rp, x);
    if (rp) {
        if (lf == lp) {lp = x; return true;}
        else if (lf == x) return true;
        if (rf == rp) {rp = x; return true;}
        else if (rf == x) return true;
    } else {
        if (lf && (lf != lp && lf != x)) return false;
        else if (lf == lp) {lp = x; return true;}
        else if (lf == x) return true;
        else if (lf) return false;
        else {rp = x; return true;}
    }
    return false;
}

inline ll GetAns(int k){ //计算每个点的答案
    int lca1 = LCA(k, lp), lca2 = LCA(k, rp);
    ll res1, res2;
    if (rp) {
        res1 = siz[lp]; res2 = siz[rp];
        if (lca1 == k || lca2 == k) return 0;
        else if (lca1 == lp) res1 -= siz[k];
        else if (lca2 == rp) res2 -= siz[k];
    } else {
        res1 = siz[lp]; res2 = siz[rp] - sp;
        if (lca1 == k || lca2 == k) return 0;
        else if (lca1 == lp) res1 -= siz[k];
        else if (lca1 == 0) res2 -= siz[k];
    }
    return res1 * res2;
}

int main(){
    scanf("%d", &t);
    while (t --) {
        scanf("%d", &n); clear();
        for (int i = 1; i < n; ++ i) {
            int u, v; scanf("%d%d", &u, &v);
            add(u, v); add(v, u);
        }
        dfs(rt = 0, 0); get_st(); pre_ans();
        /*进行所需要的预处理*/
        for (int i = head[0]; i; i = e[i].nxt)
          if (LCA(e[i].v, 1) == e[i].v) {
              sp = siz[e[i].v]; break;
          } //计算 sp
        for (int i = 2; i <= n; ++ i) {
            if (!add(i - 1)) break; 
            //尝试加入 i-1,不行就退出
            if (i == n) {ans[i] = 1; break;}
            /*如果 i == n,那么显然只存在一条路径
            贯穿整个树(其实是一条链)*/
            ans[i] = GetAns(i);
        }
        for (int i = 0; i <= n; ++ i) {
            printf("%lld ", ans[i]);
            ans[i] = 0;
        }
        printf("\n");
    }
    return 0;
}

标签:siz,return,题解,路径,Tree,else,lp,LCA,MEX
来源: https://www.cnblogs.com/Dfkuaid-210/p/CF1527D.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有