洛谷基础题-P1047 [NOIP2005 普及组] 校门外的树

NOIP2005树题解
本文针对NOIP2005普及组题目“校门外的树”,提供了三种不同的解决方案,包括暴力匹配、珂朵莉树及标记法。通过对不同算法的比较,展示了如何高效解决此类问题。

P1047 [NOIP2005 普及组] 校门外的树

1.第一次使用暴力匹配的方法解决,只能过前两个测试点

#include<iostream>
#include <cmath>
#include <vector>
using namespace std;
#define MAX 1000

int main()
{
	int roadLength,zoneNumber;
	int u_v[MAX][2];
	int u,v;
	cin>>roadLength>>zoneNumber;
	int count = 0,countRepeat = 0;
	int result;
	for (int i=0; i<zoneNumber; i++)
	{
		cin>>u>>v;
		u_v[i][0] = u;
		u_v[i][1] = v;
		for (int j=0; j<i; j++)
		{
			//注意考虑端点,+1
			//考虑可能出现的重复情况
			//将所有的重复情况计数
			if((u_v[i][0]  < u_v[j][0]))
			{
				if ((u_v[i][1]  < u_v[j][1])&&(u_v[i][1]  > u_v[j][0]))
				{
					countRepeat  +=  u_v[i][1] - u_v[j][0] + 1;
				}
				else if(u_v[i][1]  > u_v[j][1])
				{
					countRepeat  +=  u_v[j][1] - u_v[j][0] + 1;
				}
			}
			else if((u_v[i][0]  > u_v[j][0])&&(u_v[i][0]  < u_v[j][1]))
			{
				if((u_v[i][1] < u_v[j][1])&&(u_v[i][1]  > u_v[j][0]))
				{
					countRepeat  +=  u_v[i][1] - u_v[i][0] + 1;
				}
				else if(u_v[i][1] > u_v[j][1])
				{
					countRepeat  +=  u_v[j][1] - u_v[i][0] + 1;
				}
			}else{
				countRepeat  +=  0;
			}
		}
		//计算所有的树木
		count +=  u_v[i][1] - u_v[i][0] + 1;
	}
	//所有的树木减去重复的树木
	result =roadLength + 1 - (count - countRepeat);
	cout<<result<<" ";
	return 0;
}

2.第二次使用珂朵莉树数据结构,计算通过

不用单独考虑重复区域,应该做标记!!!
#include<iostream>
#include<cstdio>
#include<cstring>
#include<set>
#include<algorithm>
#define re register
#define FOR(i,l,r) for(re int i=l;i<=r;++i)
#define IT set<node>::iterator
using namespace std;

//全局变量
int n,m,x,y;

//内联函数
inline void in(re int &x){
	x=0;char c=getchar();
	while(c<'0'||c>'9'){
		c=getchar();
	}
	while(c<='9'&&c>='0'){
		x=(x<<1)+(x<<3)+(c^'0');
		c=getchar();
	}
}

inline void out(re int a){
	if(a>=10)out(a/10);
	putchar(a%10+'0');
}

//珂朵莉树
//珂朵莉树的关键在于推平一段区间,即把一段区间的数变的一样
struct node{
	//l 左节点 r 右节点
	int l,r;
	//v 值
	//*mutale,意为可变的,即不论在哪里都是可修改的,用于突破C++带const函数的限制。
	mutable int v;
	node(int L,int R=-1,int V=0):l(L),r(R),v(V) {}
	bool operator <(const node &o)const{
		return l<o.l;
	}
};

set<node> s;

/*
set::iterator split(int pos)
将原来含有pos的区间分为[l,pos)和[pos,r]两段。
返回一个std::set的迭代器,指向[pos,r]段
 */

/*
lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
upper_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
 */
inline IT split(re int pos){
	IT it=s.lower_bound(node(pos));
	if(it!=s.end()&&it->l==pos)
		return it;
	--it;
	int L=it->l;
	int R=it->r;
	int V=it->v;
	s.erase(it);
	s.insert(node(L,pos-1,V));
	return s.insert(node(pos,R,V)).first;
}

/*
首先split出l并记返回值为itl,然后split出r+1并记返回值为itr,显然我们要操作的区间
为[itl,itr),那么我们将[itl,itr)删除(std::set.erase(itl, itr)),再插入一个节点
Node,其l为l,r为r,val为赋值的val。我们注意到因为这个操作, [itl,itr)中的所有节
点合并为了一个节点,大大降低了集合的元素数量。
 */

inline void assign_val(re int l,re int r,re int val=0){
	IT itr=split(r+1),itl=split(l);
	s.erase(itl,itr);
	s.insert(node(l,r,val));
}

//区间和查询
//求和计算v=1的点数
inline int query(re int l,re int r){
	int res=0;
	IT itr=split(r+1),itl=split(l);
	for(;itl!=itr;++itl)
		res+=(itl->r - itl->l+1)*(itl->v);
	return res;
}

int main(){
	//n总的树木
	//m分段
	in(n),in(m);
	//0-n 全部置为1
	s.insert(node(0,n,1));

	//将每一段都置为0
	//关键在于不用特别考虑重复区域
	FOR(i,1,m){
		in(x),in(y);
		assign_val(x,y,0);
	}
	out(query(0,n));
	puts("");
}

3.第三次使用标记法,计算通过

#include<iostream>
#include <cmath>
#include <vector>
using namespace std;
#define MAX 100000

//将数组arr中l到r位置的值置为value
bool assignValue(int l,int r,int value,int arr[])
{
	int j;
	if (l > r)
	{
		return false;
	}
	for (j = l;j<=r;j++)
	{
		arr[j] = value;
	}
	return true;
}

//计算value =1的个数
int countValue(int l,int r,int arr[])
{
	int count, j;
	count = 0;
	for (j = l;j<=r;j++)
	{
		if (arr[j] == 1)
		{
			count += 1;
		}
	}
	return count;
}
int main()
{
	int n,m;
	cin>>n>>m;
	int valueVec[MAX];
	int u,v,count;

	assignValue(0,n,1,valueVec);
	for (int i = 0 ;i < m;i++)
	{
		cin>>u>>v;
		assignValue(u,v,0,valueVec);
	}
	count = countValue(0,n,valueVec);
	cout<<count;
	return 0;
}

<think>好的,我现在需要解决用户关于NOIP 2005普及组目P1047校门外”的解思路和代码实现的问。首先,我应该回忆目的大致内容。目是说,有条马路上有若干棵,给出些区域需要移走这些区域的,最后求剩下多少棵。这听起来像是个区间覆盖的问。 用户提供的引用中有提到,解思路是用数组表示所有,初始设为1,移走的设为0,最后统计1的数量。这个方法应该可行。那具体怎么做呢?比如,马路长度可能到10000,所以数组大小应该足够,初始化为1。然后处理每个区间,把区间内的元素置为0。最后遍历数组统计1的数量。 首先,我需要确认目中的输入格式。通常这类目第行是两个整数L和M,L代表马路的长度(即的数量是L+1,因为包括0和L点),M是区域的数量。接下来M行每行两个整数,表示区域的起始点和结束点。例如,输入可能是: 500 3 150 300 100 200 470 471 这时需要处理这三个区间,将对应的位置设为0。需要注意的是,区间可能重叠,但用数组方法的话,重复设置0也不影响结果,所以不需要特殊处理重叠。 接下来,我需要考虑如何高效地处理这些区间。例如,对于每个区间,循环从起始到结束的位置,将数组元素置0。这在L较大的情况下会不会很慢?比如L是10000的话,这样的操作时间复杂度是O(M*L),假设M是100的话,总共有1e6次操作,这在现代计算机上应该没问,尤其是对于编程竞赛来说,这样的时间复杂度是可以接受的。 然后,代码实现方面,可以用个数组,初始化所有元素为1。然后读取每个区间,循环设置对应位置为0。最后统计数组中1的个数。 比如Python代码的话,可以这样写: L, M = map(int, input().split()) trees = [1] * (L + 1) for _ in range(M): start, end = map(int, input().split()) for i in range(start, end + 1): trees[i] = 0 print(sum(trees)) 不过,当L很大时,比如L=1e5,这样的方法可能会比较慢,因为每次都要循环区间内的每个点。有没有优化的方法?比如用差分数组,或者区间合并的方法? 不过原中L的范围是多少呢?根据NOIP普及组目,通常数据规模不会太大。比如原中的L可能到10000,M到100。这样的话,上述方法完全没问。所以用户提供的引用中的思路是正确的,直接按照数组处理即可。 可能需要注意的边界条件是,区间的端点是否包含。例如,目中的区间是闭区间,所以start到end都要包括,所以在循环时要到end+1吗?或者是在Python中的range是左闭右开的,所以应该是range(start, end+1),因为i从start到end(包括end),所以循环到end+1的前位。例如,当start=100,end=200时,循环i从100到200,即range(100, 201)? 是的,这样是正确的。所以在代码中,将i从start到end(包括end),所以在Python中用range(start, end+1)。 那这个代码应该可以解决问。但有没有其他方法?比如,如果L很大,比如1e8,这时候数组会超出内存限制,此时就需要用区间合并的方法。但对于本来说,可能不需要,因为目给出的L应该是足够小的。 比如,在引用中的第二个引用提到“这个的思路是先把所有的都用数组表示出来,赋值为1,移走的用0表示,最后统计剩下的1的数量”,所以这应该就是正确的解法。用户给出的示例代码是否正确? 比如,用户提供的引用中的示例可能是个类似的问,比如P5728,但这里的P1047应该用同样的思路。因此,我认为用户提供的思路是正确的,代码应该按照这个思路来写。 总结下,解步骤: 1. 初始化数组表示每棵的状态,初始为1(存在)。 2. 遍历每个区间,将区间内的的状态设为0(移走)。 3. 统计数组中1的数量,即剩余的的数量。 需要注意的点是,数组的长度是L+1,因为包括0到L的位置。例如,当L=500时,有501棵。 现在,我需要将这个过程用Python代码写出来,并确保正确性。例如,测试样例: 输入: 500 3 150 300 100 200 470 471 处理这三个区间后,剩下的有多少? 第个区间覆盖150到300,第二个覆盖100到200,第三个覆盖470到471。合并后的覆盖范围是从100到300,以及470到471。总共有(300-100+1)=201棵,加上2棵,总共移走203棵。原本有501棵,所以剩下的501-203=298棵? 但用数组的方法的话,代码会正确计算重叠区域吗?是的,因为即使重复置0,也不会影响最终结果。 所以这段代码是正确的。 因此,用户的问需要这样的解答,给出解思路和示例代码,同时生成相关问。</think>### NOIP 2005 普及组 P1047 校门外思路与代码实现 #### 目分析 目要求在条长度为 $L$ 的马路上(包含 $0$ 到 $L$ 的整点)移除若干区间内的,最终统计剩余的数量。通过数组标记法可以高效解决此问。 #### 解步骤 1. **初始化数组**:创建长度为 $L+1$ 的数组 `trees`,所有元素初始化为 `1`,表示所有位置均有。 2. **处理区间**:对于每个输入的区间 $[u, v]$,将数组中下标为 $u$ 到 $v$ 的元素置为 `0`,表示移除该区间的。 3. **统计剩余的数量**:遍历数组,统计 `1` 的数量。 #### 示例代码 ```python L, M = map(int, input().split()) trees = [1] * (L + 1) for _ in range(M): start, end = map(int, input().split()) for i in range(start, end + 1): trees[i] = 0 print(sum(trees)) ``` #### 代码说明 - **输入处理**:首行输入 $L$ 和 $M$,分别表示马路长度和区间数量。 - **数组初始化**:`trees` 数组的长度为 $L+1$,对应 $0$ 到 $L$ 的所有位置。 - **区间处理**:通过双重循环标记移除的,时间复杂度为 $O(M \cdot \text{平均区间长度})$,适用于目给定的数据范围。 - **结果输出**:直接对数组求和,统计剩余的。 #### 优化思考 若 $L$ 极大(如 $10^8$),数组会超出内存限制。此时可通过**区间合并**优化: 1. 将所有区间按起点排序并合并重叠部分。 2. 计算合并后各区间的总覆盖长度,最终剩余数为 $L+1 - \text{总覆盖长度}$[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值