贪心之种树(区间选点问题)

       描述

一条街的一边有几座房子。因为环保原因居民想要在路边种些树。路边的地区被分割成块,并被编号为1..n。每个块大小为一个单位尺寸并最多可种一棵树。每个居民想在门前种些树并指定了三个号码b,e,t。这三个数表示该居民想在b和e之间最少种t棵树。当然,b<=e,居民必须保证在指定地区不能种多于地区被分割成块数的树,即要求t<=e-b+1。允许居民想种树的各自区域可以交叉。出于资金短缺的原因,环保部门请你求出能够满足所有居民的要求,需要种树的最少数量。

输入
共n+1行
第1行:输入一个n,表示有n户居民提出要求要在门前种树(1≤n≤100)
后n行:每行输入3个数bi、ei和ti,用空格隔开,分别表示第i户居民想在门前的起点bi到终点ei(包括端点),想种ti颗树(1≤bi≤ei≤100)
输出
满足n户居民的最少种树量
样例输入
3
1 3 2
2 6 3
7 10 1
样例输出
4
提示
贪心(区间问题)
解题思路:
首先题目要求是居民想种的树的各自区域可以相交,并且请你求出能够满足所有居民的要求,需要种树的最少数量,所以就可以想到尽量在相交的部分种树可以使种树的数量最少。由于每位居民与上一位居民的相交处位于上一位居民的尾部,所以我们可以考虑从尾部开始种树。排序的过程是先按照每户居民的结束位置从小到大排列,如果结束位置相等,则按照开始位置从大到小排列。

    样例程序:

#include<iostream>
#include<algorithm>
using namespace std;
struct home//结构体中包括b,e,t
{
	int b,e,t;
}c[105];
int flag[10005]={0},sum;//flag用来表示该位置是否有树
int gs(int x,int y)//种树函数,从尾部y开始倒着种树,种x棵
{
	while(y)
	{
		flag[x]=1;
		x--;y--;
	}
	return 0;
}
int ss(int x,int y)//数树函数,用来计算x到y之间有多少棵树
{
	sum=0;
	for(int i=x;i<=y;i++)
		if(flag[i]==1)
			sum++;
	return sum;
}
int cmp(home x,home y)//排序函数
{
	if(x.e<y.e) return 1;//先按照结束位置从小到大排列
	else if(x.e==y.e&&x.b>y.b) return 1;//结束位置相同,把开始位置大的放在前面
		else return 0;
}
int main()
{
	int n,i,tot=0,tmp;//tot表示所要种的树的总数
	cin>>n;
	for(i=1;i<=n;i++)
		cin>>c[i].b>>c[i].e>>c[i].t;
	sort(c+1,c+1+n,cmp);//进行排序
	gs(c[1].e,c[1].t);//为第一户居民种树
	tot+=c[1].t; 
	for(i=2;i<=n;i++)
	{
		if(c[i].b<=c[i-1].e)//如果这个居民的种树的开始位置小于上一个居民的结束位置,有相交区域
		{
			tmp=ss(c[i].b,c[i-1].e);//计算出相交位置有多少棵树(tmp)
			if(tmp>=c[i].t)//如果大于该居民所要种植的树,则不需要在种树
				continue;
			else
				gs(c[i].e,c[i].t-tmp);//否则再从该居民的尾部开始种树,数量为c[i].t-tmp
				tot+=c[i].t-tmp;//改变tot的值
		}
		else//如果没有相交的区域
		{
			gs(c[i].e,c[i].t);//从该居民的结束位置倒着种树
			tot+=c[i].t;
			
		}
	}
	cout<<tot;
	return 0;
}

### 关于区间选点问题的C++贪心算法实现 #### 问题描述 区间选点问题是经典的贪心算法应用场景之一。其目标是在数轴上选择尽可能少的点,使得这些点可以覆盖所有给定的区间。 #### 算法核心思想 贪心算法的核心在于每次做出局部最优的选择,期望最终达到全局最优的结果。对于区间选点问题,可以通过优先处理右端点较小的区间来减少所需选取的点的数量[^1]。 以下是基于上述原理的一个完整的C++实现: ```cpp #include <iostream> #include <algorithm> using namespace std; const int N = 100010; struct Range { int l, r; bool operator<(const Range& W) const { return r < W.r; // 按照区间的右端点升序排列 } } range[N]; int main() { int n; cin >> n; for (int i = 0; i < n; i++) { cin >> range[i].l >> range[i].r; } // 对区间按照右端点进行排序 sort(range, range + n); int res = 0; // 记录所选点的数量 int ed = -2e9; // 当前已覆盖的最大位置 for (int i = 0; i < n; i++) { if (range[i].l > ed) { // 如果当前区间的左端点大于已覆盖的最大位置 res++; // 增加一个新点 ed = range[i].r; // 更新最大覆盖范围到该区间的右端点 } } cout << res << endl; // 输出最少需要的点数 return 0; } ``` #### 代码解析 1. **结构体定义** 定义了一个`Range`结构体用于存储每个区间的左右边界,并重载了小于运算符`<`以便按右端点从小到大排序[^2]。 2. **输入读取与排序** 输入所有区间并将其按右端点升序排列,这样可以从最小的右端点开始逐步扩展覆盖区域[^4]。 3. **遍历与判断** 使用变量`ed`记录当前已经通过选定的点所能覆盖的最远位置。如果某个区间的左端点超过了这个位置,则说明需要新增一个点来覆盖此区间[^5]。 4. **结果输出** 最终输出所需的最少点数作为答案。 #### 正确性证明 为了验证这种策略的有效性,假设存在一种其他方式可以在某一步获得更优解(即选择了不同的点),但由于我们总是挑选最早结束的区间去更新下一个起点的位置,因此不可能找到比这种方法更好的解决方案[^3]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值