AtCoder - ARC 148 - C(思维)

C - Lights Out on Tree

题意:

有一个以 1 为根,共有 N 个节点的树,给出节点 i(2~N)的父节点 Pi。

规定每个节点的初始状态为正,可以通过按钮 i(1~N)对节点 i 进行翻转操作。

给你 Q 个询问,每个询问有 M 个数,表示最终这 M 个节点的状态必须是反的,问最小的操作次数。

数据范围:

​2 ≤ N ≤ 2 × 10^{5}
1 ≤ Pi​ < i
1 ≤ Q ≤ 2 × 10^{5}
1 ≤ Mi​

\sum_{i =1}^{Q}Mi<2*10^{5}
1 ≤ vi, j ​≤ N

思路:

如图所示,规定树中结点1,4是需要翻转(这里及之后所说的需要翻转到的节点指的是要求最终状态为反)的节点。
            1
         /   |   \
       2   3    4
                 /   \
                5    6
先考虑节点1为父节点和1的子节点2,3,4——因为节点1需要翻转,所以先对节点1进行翻转操作,先不考虑子节点的要求,全部按照不需要翻转进行还原操作,即对节点2,3,4再进行翻转操作;
再判断子节点中哪些是需要翻转的,根据要求可以看出子节点中节点4是需要翻转的节点,而此时的状态是原状态,所以需要对节点4再进行翻转操作一次;
再考虑节点4为父节点和它的子节点5,6——再对节点5,6进行还原操作。至此,节点1,4都已经是翻转状态,而其他节点是原状态,操作完毕。

我们再回过头来看整个过程,这样总共的操作次数是:1(节点1的翻转操作)+3(节点2,3,4的还原操作)+1(节点4的翻转操作)+2(节点5,6的还原操作)=7次;其中节点1的翻转操作和2,3,5,6的还原操作都是必须进行的,而节点4的状态变化,除了第一次翻转节点1时使得节点4的状态变化后,总共又对它自己单独操作了它2次,而这2次翻转操作整体对节点4相当于没操作,所以这2次操作是不需要计入总操作数的。也就是说,总操作数为1+3-2+1+2=5。即总操作数 = 每个需要翻转的节点及其子节点个数 - 子节点中需要翻转的节点个数*2

实现:

1. 因为需要该节点(指的是需要翻转的节点,即每次当做子树的子节点的节点)的子节点个数,所以需要数组 son[i] 记录节点 i 的子节点个数;因为需要判断该节点的子节点是否是要翻转的节点,等价于判断需要翻转节点中是否存在某节点的父节点是该节点即可,所以需要数组 fa[i] 记录节点i的父节点
2. 预处理完数组 fa[], son[] 后,将需要翻转的数组存在 s 中,顺次遍历,每遍历到一个节点,即考虑该节点及其子节点,操作数算上该子树大小;再判断是否s数组中存在节点的父节点是该节点,如果存在,操作数减2。以此类推,直到遍历完所有需要翻转节点。

Code:

#include<bits/stdc++.h>
using namespace std;

const int N = 200010;

int n, q;
int fa[N], son[N];

int main()
{
    cin >> n >> q;

    for (int i = 2; i <= n; i++)
    {
        cin >> fa[i];                 //数组fa[i]记录节点i的父节点
        son[fa[i]]++;                 //数组son[i]记录节点i的子节点个数
    }

    //输入q个询问
    while (q--)
    {
        int m;
        set<int> s;
        cin >> m;                        // 输入需要翻转的节点个数
        
        for (int i = 0; i < m; i++)
        {
            int x;
            cin >> x;
            s.insert(x);                 // 将需要翻转的节点存在s中
        }

        int res = 0;
        for (auto it : s)
        {
            res += son[it] + 1;          // 算上需要翻转的节点(1)及其子节点的个数son[it]
            if(s.count(fa[it]))res-=2;   // 如果出现某个节点it的子节点(也就是父节点为it的节点fa[it])是需要翻转的节点,那么就执行一次操作数-2
        }

        cout << res << "\n";
    }

    return 0;
}

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
吐槽:这还是第一次写ARC的题,果然好难,我菜得听学长讲两遍才听懂qwq,不过这题真不戳。题目注意点:

1. son[fa[i]]++;这里记录节点i的子节点个数阔以这样直接求,之前没有过,真妙啊。

2. 还有res-=2这一块是本题的精髓。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值