01 Tree

Hello 2024
D. 01 Tree
题目链接

CodeForces Hello 2024的D题

这场难度很大,难度分布800-800-1400-2100-2600,好好的年不让人过了是吧。
在这里插入图片描述
改到红温。

题意:

给你n个同属于一棵树的叶子节点的值,给出的顺序就是先序遍历到叶子节点的顺序。

这个树有些特殊性质:

  1. 二叉树,而且除了叶子节点,其他节点一定有两个儿子节点
  2. 连向两个儿子节点的边有一个权值为0,另一个权值为1
  3. 节点的值就是节点到根节点的权值之和

问给你n个数,存不存在这样的一棵树满足条件。

思路:

其实可以把题意转化成和树完全没有关系的东西,比如:

两个相邻的数如果差一就可以合并,得到两个数里小的那个,问最后能不能就剩下一个0。

如果存在这样的一颗树的话,那么我们可以选择最深的的两个节点,这样一个节点是最大值,另一个是最大值-1,合并成父节点,我们把这两个叶子节点删掉,父节点成为新的叶子节点,不停这样做,最后就会剩下根节点,也就是0。

不过我们一定得保证原数列里有且只有一个0,多了会导致多余的0无法合并,少了会导致最后合并不到0。如果能保证,并且能合并到最后一个数,那么最后一定是剩余一个0。

考虑如何取得这个最大值,考虑用一个set存储<值,位置(下标)>二元组,就可以每次取出set的end()-1,也就是位置最靠后的最大值,然后可以知道这个最大值val和它的位置idx,然后查询<val-1,idx±1>存不存在就行了。

虽然可以保证所有最大值里至少有一个旁边有最大值-1的可以合并,但是如果只看最后一个最大值是有特殊情况的,就是 0 1 1 1 1 1这种,几个最大值挨在一起,而且最大值-1在左边。

因为如果有一个最大值无法合并,那么其他的就算合并了,这边之后也不可能合并的了了,所以要保证有解,最大值旁边一定至少有一边有是最大值或者最大值-1,又因为我们拿到的是最后一个最大值,所以我们看到后面没有,就往前找,一直找到 最大值-1,然后删掉这个数后面的最大值就行了,否则就无解。

我们要往前找的时候,原来的set就没法用了,idx在第二维,除非知道第一维是啥,要不然找不到这个数,数组删掉数后之后再找到的话会很浪费时间,所以就需要用到一个双向链表,用个nxt和pre数组实现一个模拟双向链表即可。我们只需要知道前导和后继节点的编号就行了,因为我们找的是要么是val或者val-1,否则就无解。

不用双向链表还可以使用set来实现一个类双向链表的功能,而且功能更加强大:

set存储一个pair,第一维存储下标,这样set会自动按第一维也就是下标来排序(下标的相对顺序就是链表里的相对顺序),通过迭代器我们可以访问一个节点的前导和后继的节点。

set可以添加,删除一个节点。时间复杂度是 O ( n l g o n ) O(nlgon) O(nlgon),略差于双向链表。不过 l o g n ≈ 20 logn\approx 20 logn20,也不是不能接受。不过还是比较容易卡掉的,不过一般情况下我们手搓模拟链表一般也不会去用list,因为模拟链表用的数组,可以实现 O ( 1 ) O(1) O(1) 的随机访问某个下标,链表list却做不到。

重点在于查询,set实现的类双向链表可以使用count,find,begin,end,lower_bound,upper_bound成员函数,而且都是 O ( l o g n ) O(logn) O(logn) O ( 1 ) O(1) O(1) 的,爆杀链表。set迭代器++ --的时间复杂度最坏情况下是 O ( l o g n ) O(logn) O(logn),平均情况下一般看作是一个较大的常数级。

偏了偏了,话说回来,往前找这一步可以被卡成 O ( n ) ∼ O ( n l o g n ) O(n)\sim O(nlogn) O(n)O(nlogn)的,总的会就被卡成 O ( n 2 ) ∼ O ( n 2 l o g n ) O(n^2)\sim O(n^2logn) O(n2)O(n2logn)的,肯定会T,于是就变成了上面的TLE 11。

所以我们要加一个重要的优化!

考虑到0 1 1 1 1 1这种,左边有再多最大值都没有用,最后都是最大值-1 从左到右一个一个合并掉,所以不如直接一步到位,向左移的时候顺便把路过的最大值都删掉,这样移动一次就会删掉一个元素,总的时间复杂度就是 O ( n l o g n ) O(nlogn) O(nlogn) 了。

code:

(模拟双向链表+set(有重要优化)):280ms

#include <iostream>
#include <cstdio>
#include <set>
using namespace std;
const int maxn=2e5+5;

int T,n;
pair<int,int> a[maxn];
int pre[maxn],nxt[maxn];
set<pair<int,int> > s;

void p(pair<int,int> x){
	printf("%d %d\n",x.first,x.second);
}
void p(){
	for(int i=nxt[0];i;i=nxt[i])
		cout<<i<<"-\n"[!nxt[i]];
	for(auto &x:s)
		p(x);
}

int main(){
	cin>>T;
	while(T--){
		cin>>n;
		s.clear();
		bool fx[2]={false};
		for(int i=1,t;i<=n;i++){
			cin>>t;
			if(t==0){
				if(!fx[0])fx[0]=true;
				else fx[1]=true;
			}
			
			s.insert(make_pair(t,i));
			pre[i]=i-1;
			nxt[i-1]=i;
		}
		if(!fx[0] || fx[1]){
			puts("NO");
			continue;
		}
		bool f=false;
		while(s.size()>1){
			auto it=s.end();
			it--;
			int val=(*it).first,idx=(*it).second,it1;
			
			if(s.count(make_pair(val-1,nxt[idx]))){
				s.erase(it);
				nxt[pre[idx]]=nxt[idx];
				pre[nxt[idx]]=pre[idx];
			}
			else {
				it1=pre[idx];
				while(it1 && s.count(make_pair(val,it1)))it1=pre[it1];
				it1=nxt[it1];
				if(s.count(make_pair(val-1,pre[it1]))){
					it=s.find(make_pair(val,it1));
					s.erase(it,s.end());
					nxt[pre[it1]]=nxt[idx];
					pre[nxt[idx]]=pre[it1];
				}
				else {
					f=true;
					break;
				}
			}
		}
		puts((!f)?"YES":"NO");
	}
	return 0;
}

双set(傻了,只在开头进行了一次重要优化,不过移动的时候 O ( n ) O(n) O(n)好像没有被卡掉):342ms

#include <iostream>
#include <cstdio>
#include <set>
using namespace std;
const int maxn=2e5+5;

int T,n;

void p(pair<int,int> x){
	printf("%d %d\n",x.first,x.second);
}

int main(){
	cin>>T;
	while(T--){
		cin>>n;
		set<pair<int,int> > s1,s2;
		bool f1=false,f2=false;
		for(int i=1,t,lst=-1;i<=n;i++){
			scanf("%d",&t);
			if(t==0){
				if(!f1)f1=true;
				else f2=true;
			}
			if(t==lst)continue;
			else lst=t;
			s1.insert(make_pair(i,t));
			s2.insert(make_pair(t,i));
		}
		if(f2){
			puts("NO");
			continue;
		}
		bool f=false;
		int val,idx;
		set<pair<int,int> >::iterator it,it1,it2;
		for(int i=1;s1.size()>1;i++){
			it=s2.end();it--;
			
			val=(*it).first;
			idx=(*it).second;//取到的一定是最后一个最大值 
//			p(*it);
			it2=it1=s1.find(make_pair(idx,val));
			
			it1++;
			if(it1!=s1.end() && (*it1).second==val-1){
				it1--;
//				it=s2.find(make_pair(val,(*it1).first));
				s1.erase(it1);
				s2.erase(it);
			}
			else {
				while(it2!=s1.begin() && (*it2).second==val)it2--;
				if((*it2).second==val-1){
					it2++;
					it=s2.find(make_pair(val,(*it2).first));
					s1.erase(it2);
					s2.erase(it);
				}
				else {
					f=true;
					break;
				}
			}
		}
		puts((!f && (*s1.begin()).second==0)?"YES":"NO");
	}
	return 0;
}
  • 17
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值