这题本身是非常简单的,比赛时是打算用差分来写的,但是差分在处理操作3和操作4时挺不方便的,在看到题解时才意识到可以差分数组和权重数组一起用,不仅好理解还不容易出错。原来的方法死于debug失败,特地写此文警醒自己。
题面:
链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
牛牛有一颗包含 n 个结点的二叉树,这些结点编号为 1…n 。这颗树被定义为:
1、以结点 1 为根。
2、编号为 x 结点的两个儿子编号分别为: 2×x和2×x+1。
3、每个结点的权重初始都为 0。
牛牛接下来会对这颗树进行 m 次操作,操作的格式是以下四种之一:
1、op x (这里 op=1 )代表牛牛将以编号 x 为根结点的子树中所有结点的权重 +1。
2、op x(这里 op=2 )代表牛牛将以编号 x 为根结点的子树外的所有结点权重 +1。
3、op x(这里 op=3 )代表牛牛将根结点到编号 x 结点的路径上的所有结点权重 +1。
4、op x(这里 op=4 )代表牛牛将根节点到编号 x 结点的路径上的结点之外的所有结点权重+1。
牛妹想知道当牛牛的所有操作结束之后,树中权重为 0,1,2…m 的结点的数量分别是多少。
示例输入:
7 4
1 2
3 5
4 3
2 7
示例输出:
0 2 2 1 2
先贴差分数组和权重数组一起用的代码:
#include<cstdio>
#include<iostream>
using namespace std;
int a[20000010]/*差分*/,b[20000010]/*权重*/,cnt[500005]/*计数*/;
int n,m,op,x;
int main(){
cin>>n/*结点数*/>>m/*操作数*/;
for(int i=0;i<m;i++){
scanf("%d%d",&op,&x);
if(op==1){//op=1 代表牛牛将以编号 x 为根结点的子树中所有结点的权重 +1
a[x]++;
}
else if(op==2){//op=2 代表牛牛将以编号 x为根结点的子树外的所有结点权重 +1。
a[1]++;
a[x]--;
}
else if(op==3){//op=3 代表牛牛将根结点到编号 x 结点的路径上的所有结点权重 +1
while(x>0){
b[x]++;
x=x/2;
}
}
else{//op=4 代表牛牛将根节点到编号 x 结点的路径上的结点之外的所有结点权重 +1
a[1]++;
while(x>0){
b[x]--;
x=x/2;
}
}
}
for(int i=1;i<=n;i++){
a[i*2]+=a[i];
a[i*2+1]+=a[i];
b[i]+=a[i];
cnt[b[i]]++;
}
for(int i=0;i<=m;i++){
cout<<cnt[i]<<' ';
}
return 0;
}
如果能想到差分的话操作1和操作2是没问题的,直接对结点x进行对应操作就可以了。操作3和操作4是对特定路径上的结点进行操作,因为是标准的二叉树所以直接子结点编号除以2就是父结点的编号。直接在路径上的结点+1或者-1就行了。
接下来贴只用差分数组的写法:
#include<cstdio>
#include<iostream>
using namespace std;
int v[20000010],cnt[500005];
int n,op,x,m;
int main(){
cin>>n>>m;
for(int k=0;k<m;k++){
scanf("%d%d",&op,&x);
if(op==1){//op=1 代表牛牛将以编号 x 为根结点的子树中所有结点的权重 +1
v[x]++;
}
else if(op==2){//op=2 代表牛牛将以编号 x为根结点的子树外的所有结点权重 +1。
v[1]++;
v[x]--;
}
else if(op==3){//op=3 代表牛牛将根结点到编号 x 结点的路径上的所有结点权重 +1
v[1]++;
v[x*2]--;
v[x*2+1]--;
while(x>0){
v[x+1-2*(x%2)]--;//如果x是左儿子即是偶数则v[x+1]--,反之v[x-1]--;
x=x/2;
}
}
else{//op=4 代表牛牛将根节点到编号 x 结点的路径上的结点之外的所有结点权重 +1
v[x*2]++;
v[x*2+1]++;
while(x>1){
v[x+1-2*(x%2)]++;/*同理op=3*/
x=x/2;
}
}
}
for(int i=1;i<=n;i++){
v[i*2]+=v[i];
v[i*2+1]+=v[i];
cnt[v[i]]++;
}
for(int i=0;i<=m;i++){
cout<<cnt[i]<<' ';
}
return 0;
}
与别人的方法差距只在操作3和操作4上,因为是标准的二叉树,所以可以凭奇偶判断出是左儿子还是右儿子,可以通过祖结点+1,兄弟结点-1的方法使路径上的点+1,同理实现排除路径上的点+1。