牛客小白月赛62E题剖分 题解

这题本身是非常简单的,比赛时是打算用差分来写的,但是差分在处理操作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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值