Codeforces 877E Danil and a Part-time Job

本题的做法是dfs序线段树。

1.什么是dfs序?
dfs从根节点遍历整棵树的过程中经过的节点编号的顺序就是dfs序。
2.如何将dfs序与线段树结合在一起?
对得到的dfs序与1-n进行映射,可以得到一个初始数组num。将dfs序中的第i个节点的标号记为x,那么num[i]表示的就是节点x上灯泡的开关情况。不难发现同一颗子树上的所有节点在dfs序中代表一个连续的区间。那么,以x为根节点子树灯泡情况的反转其实就是num数组中一段连续区间全部异或上1。那么就是一颗简单的区间修改区间查询线段树啦

下面贴上AC代码

#include<bits/stdc++.h>
#define maxn 200000 + 10
using namespace std;
struct node {
    int sum;//当前区间的灯泡开启数
    int lazy;
}tree[maxn << 2];
int pos[maxn], npos[maxn], cnt;//pos用来记录节点x映射后的位置p npos用于记录位置p所代表的节点x
int st[maxn],num[maxn];//记录各个节点的初始灯泡开关情况 以及以x节点为根节点的子树中含有的节点数
int n, tmp;
vector<int>e[maxn];
void dfs(int x)//找出dfs序列
{
    num[x] = 1;
    pos[x] = ++cnt; npos[cnt] = x;
    for (int i = 0; i < e[x].size(); i++) {
        dfs(e[x][i]);
        num[x] += num[e[x][i]]; //统计以x为根节点的子树的节点数目
    }
}
void build(int x, int L, int R)//普通的线段树建树过程
{
    if (L == R) {
        tree[x].sum = st[npos[L]];//节点编号为npos[L]的节点上灯泡的亮暗情况
        return;
    }
    int mid = L + R >> 1;
    build(x << 1, L, mid);
    build(x << 1 | 1, mid + 1, R);
    tree[x].sum = tree[x << 1].sum + tree[x << 1 | 1].sum;
}
void update(int cur, int L, int R)//向下更新
{
    int mid = L + R >> 1;
    tree[cur << 1].sum = mid - L + 1 - tree[cur << 1].sum;//左子树
    tree[cur << 1 | 1].sum = R - mid - tree[cur << 1 | 1].sum;
    tree[cur << 1].lazy ^= 1;//节点传递到左右孩子
    tree[cur << 1 | 1].lazy ^= 1;
    tree[cur].lazy = 0;//向下更新完成
}
int query(int cur, int L, int R, int x, int y) //由于dfs序,如果区间长度y-x+1等于以x为根节点的子树的节点数,可以保证区间[x,y]上的所有点均属于一颗子树
{
    if (x == L&&y == R) return tree[cur].sum;
    int mid = L + R >> 1;
    if (tree[cur].lazy) update(cur, L, R);
    if (mid >= y)return query(cur << 1, L, mid, x, y);//向左查询
    else if (mid + 1 <= x)return query(cur << 1 | 1, mid + 1, R, x, y);//向右查询
    else return query(cur << 1, L, mid, x, mid) + query(cur << 1 | 1, mid + 1, R, mid + 1, y);
}
void modify(int cur, int L, int R, int x, int y)
{
    if (L == x&&R == y) {
        tree[cur].sum = y - x + 1 - tree[cur].sum;
        tree[cur].lazy ^= 1;
        return;
    }
    int mid = L + R >> 1;
    if (tree[cur].lazy)update(cur, L, R);
    if (mid >= y)modify(cur << 1, L, mid, x, y);//向左查询
    else if (mid + 1 <= x)modify(cur << 1 | 1, mid + 1, R, x, y);
    else modify(cur << 1, L, mid, x, mid), modify(cur << 1 | 1, mid + 1, R, mid + 1, y);
    tree[cur].sum = tree[cur << 1].sum + tree[cur << 1 | 1].sum;
}
int main()
{
    int n; scanf("%d", &n);
    for (int i = 2; i <= n; i++) {
        scanf("%d", &tmp);
        e[tmp].push_back(i);//建立以1为根节点的树
    }
    for (int i = 1; i <= n; i++)scanf("%d", &st[i]);//记录n个节点的初始灯泡开关情况
    dfs(1);
    build(1, 1, n);//线段树建树
    int q; scanf("%d", &q);
    char ord[15];
    int p;
    while (q--) {
        scanf("%s%d", ord, &p);
        if (ord[0] == 'g')printf("%d\n", query(1, 1, n, pos[p], pos[p] + num[p] - 1));
        else modify(1, 1, n, pos[p], pos[p] + num[p] - 1);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值