開始想了一個錯誤的算法,對每個節點保存它的子樹中的節點數目和其中爲一的數目,操作某節點時向上更新其祖先的數據(log N),查詢時直接輸出。當時忽略了操作同時也需要更新後代的數據,這樣的最壞複雜度是N,肯定是不行的。但直接交上去不是WA而是超時,由此猜想其中有類似鏈表的數據。
這個問題可以用線段樹很好地解決,在對原樹進行先序遍歷的同時標上序號後,可以將任意子樹轉化爲區間。序號介於某節點到它的任意後代間的點必爲該節點的後代,在這種排序下即可建立線段樹,將對子樹的操作轉化爲對區間的操作。
#include <stdio.h>
#include <memory.h>
#define N 100001
#define M 262144
#define Negate(x) label[x]=!label[x]
#define Comple(a, b) sum[a]=b-sum[a]
int right[N];
int first[N];
int next[N];
int num[N];
int sum[M];
bool label[M];
int a, b, ans;
int n, m, cnt;
void build(int index)
{
int i = first[index];
num[index] = ++ cnt;
right[index] = cnt;
while(i > 0)
{
build(i);
if(right[i] > right[index])
right[index] = right[i];
i = next[i];
}
}
void update(int cur, int l, int r)
{
if(a<=l && b>=r)
{
Negate(cur);
Comple(cur, r-l+1);
}
else
{
int mid = (l+r) >> 1;
int x = cur<<1, y = x+1;
if(label[cur] == true)
{
Negate(cur);
Negate(x);
Negate(y);
Comple(x, mid-l+1);
Comple(y, r-mid);
}
if(a <= mid)
update(x, l, mid);
if(b > mid)
update(y, mid+1, r);
sum[cur] = sum[x] + sum[y];
}
}
void query(int cur, int l, int r)
{
if (a<=l && b>=r)
ans += sum[cur];
else
{
int mid = (l+r) >> 1;
int x = cur<<1, y = x+1;
if(label[cur] == true)
{
Negate(cur);
Negate(x);
Negate(y);
Comple(x, mid-l+1);
Comple(y, r-mid);
}
if(a <= mid)
query(x, l, mid);
if(b > mid)
query(y, mid+1, r);
sum[cur] = sum[x] + sum[y];
}
}
int main()
{
int node;
while(scanf("%d%d", &n, &m) != EOF)
{
memset(first, 0, sizeof(first));
for(int i=2; i<=n; ++i)
{
scanf("%d", &node);
next[i] = first[node];
first[node] = i;
}
cnt = 0;
build(1);
memset(label, false, sizeof(label));
memset(sum, 0, sizeof(sum));
char c[5];
for(int j=0; j<m; ++j)
{
scanf("%s", c);
scanf("%d", &node);
a = num[node];
b = right[node];
if(c[0] == 'q')
{
ans = 0;
query(1, 1, n);
printf("%d\n", ans);
}
else if(c[0] == 'o')
update(1, 1, n);
}
printf("\n");
}
return 0;
}