POJ - 3321
目錄
題意:
給出一個蘋果樹,每個節點一開始都有蘋果。有兩種操作:
C X,如果X點有蘋果,則拿掉,如果沒有,則新長出一個
Q X,查詢X點與它的所有後代分支一共有幾個蘋果
分析:
給我邊的信息,用dfs爲這棵樹編號,並記錄每個結點的管轄範圍(即線段),查詢該結點子樹的和其實就是查詢這個線段區間的和。即,就可以用線段樹或樹狀數組求解了。
這題用不能用cin 關閉同步流也不行,必須用scanf
一、用樹狀數組求解
分析:
1、輸入條件中告訴我們有n個節點 n-1個鄰接關係,首先用鄰接表(用vector數組)存儲邊的信息。
2、因爲不是標準的二叉樹,然後通過dfs搜索的方式爲每一個結點編號,至此我的理解就是我們根據題目中邊的信息構建出了一棵有編號的樹。另外在編號的過程中,給每個結點賦一個左值和右值(其中左值的意思就是該結點的編號,右值就是子樹中的最大編號),用左值和右值表示這個結點的管轄範圍(就類似於線段樹中的一條線段)。
下面圖片內容轉載自 鏈接
上圖也就是DFS搜索的時候做標記的過程,這樣新的編號爲1~6的節點所管轄的範圍分別就是[1,6] [2,4] [3,3] [4,4] [5,6] [6,6],其中左邊的是左值,右邊的是右值,節點1的區間是[1,6],正好這棵子樹有6個節點,其他也一樣。
3、所以我們求該節點子樹中的蘋果數時,就用到樹狀數組的sum()函數,式子爲sum(rig[x])-sum(lef[x]-1)意思爲求出每一個節點從1~左值的和 和 1~右值的和 他們的差就是這個節點的子樹的所有的和(即這棵子樹蘋果數目)
看到兩篇非常好的題解
#include<iostream>
#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<string>
#include<vector>
using namespace std;
const int maxn=1e5+10;
int kase;
int n,m;
int c[maxn];
int lef[maxn],rig[maxn];
int a[maxn];//表示每個結點的值,相當於樹狀數組中的葉子節點數組
vector<vector<int> > v(maxn);
void dfs(int x)
{
lef[x]=kase;//爲左值編號,就是該節點編號
int len=v[x].size();
for(int i=0;i<len;i++)
{
kase++;
dfs(v[x][i]);//遞歸地爲結點x的孩子結點編號
}
/*x的孩子結點都編號完畢之後,設置x結點的右值*/
rig[x]=kase;
}
int lowbit(int x){return x&(-x);}
void add(int x,int num)
{
for(int i=x;i<=n;i+=lowbit(i))
c[i]+=num;
}
int sum(int x)
{
int ans=0;
for(int i=x;i>0;i-=lowbit(i))
ans+=c[i];
return ans;
}
int main()
{
// freopen("in.txt","r",stdin);
while(~scanf("%d",&n))
{
int x;
int e1,e2;
char ch[3];
memset(c,0,sizeof(c));
memset(lef,0,sizeof(lef));
memset(rig,0,sizeof(rig));
memset(a,0,sizeof(a));
for(int i=0;i<maxn;i++)
v[i].clear();
for(int i=1;i<=n;i++)
{
a[i]=1;
add(i,1);//題目要求初始都爲1
}
for(int i=1;i<n;i++)
{
scanf("%d%d",&e1,&e2);
v[e1].push_back(e2);//保存邊的信息
}
kase=1;
dfs(1);
scanf("%d",&m);
for(int i=0;i<m;i++)
{
scanf("%s%d",ch,&x);
if(ch[0]=='Q')
{
printf("%d\n",sum(rig[x])-sum(lef[x]-1));
}
else
{
if(a[x]==1)
add(lef[x],-1);///這裏必須用lef[x],因爲x不一定就等於lef[x]
else
add(lef[x],1);
a[x]=!a[x];
}
}
}
return 0;
}
/*
測試樣例:
5
1 2
1 3
3 5
3 4
10
Q 1
C 2
Q 1
Q 2
C 2
Q 1
C 5
Q 5
C 5
Q 5
*/
二、用線段樹求解
用線段樹做其實也挺簡單的 雖然讓我糾結很久吧。 需要寫兩個函數,單點修改和區間查詢 因爲是單點修改 所以就不用寫懶惰標記了,這個地方也是糾結很久纔想通。
看到一篇文章也是用線段樹做的但是他保存邊信息的方法是鏈式向前星法 而我用的是vector數組。我看到一些題解說用vector建圖會卡STL,用這個方法就不會,希望以後注意一下這一點。 文章鏈接。
#include<iostream>
#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<string>
#include<vector>
using namespace std;
const int maxn=1e6;
int kase;
int n,m;
int lef[maxn],rig[maxn];
int a[maxn];
int sum[maxn];
vector<vector<int> > v(maxn);
void dfs(int x)
{
lef[x]=kase;
int len=v[x].size();
for(int i=0;i<len;i++)
{
kase++;
dfs(v[x][i]);//遞歸地爲結點x的孩子結點編號
}
/*x的孩子結點都編號完畢之後,設置x結點的右值*/
rig[x]=kase;
}
void pushup(int k)
{
sum[k]=sum[2*k]+sum[2*k+1];
}
void build(int l,int r,int k)
{
if(l==r)
{
sum[k]=1;
return;
}
int mid=(l+r)/2;
build(l,mid,2*k);
build(mid+1,r,2*k+1);
pushup(k);
}
void updateone(int l,int r,int k,int p,int add)
{
if(l==r)
{
sum[k]+=add;
return;
}
int mid=(l+r)/2;
if(p<=mid) updateone(l,mid,2*k,p,add);
else updateone(mid+1,r,2*k+1,p,add);
pushup(k);
}
int query(int L,int R,int l,int r,int k)//L,R表示要查詢的區間,l,r就表示當前的區間
{
if(l>=L&&r<=R)
return sum[k];
int ans=0;
int mid=(l+r)/2;
if(mid>=L) ans+=query(L,R,l,mid,2*k);
if(mid<R) ans+=query(L,R,mid+1,r,2*k+1);
pushup(k);
return ans;
}
int main()
{
// freopen("in.txt","r",stdin);
while(~scanf("%d",&n))
{
int x;
int e1,e2;
char ch[3];
memset(lef,0,sizeof(lef));
memset(rig,0,sizeof(rig));
memset(a,0,sizeof(a));
memset(sum,0,sizeof(sum));
for(int i=0;i<maxn;i++)
v[i].clear();
for(int i=1;i<=n;i++)
a[i]=1;
for(int i=1;i<n;i++)
{
scanf("%d%d",&e1,&e2);
v[e1].push_back(e2);//保存邊的信息
}
kase=1;
dfs(1);
build(1,n,1);
scanf("%d",&m);
for(int i=0;i<m;i++)
{
scanf("%s%d",ch,&x);
if(ch[0]=='Q')
{
printf("%d\n",query(lef[x],rig[x],1,n,1));
}
else
{
if(a[x]==1)
updateone(1,n,1,lef[x],-1);
else
updateone(1,n,1,lef[x],1);
a[x]=!a[x];
}
}
}
return 0;
}