【2024.2.5练习】砍竹子(25分)

题目描述

【2024.2.5练习】砍竹子(25分)_第1张图片

【2024.2.5练习】砍竹子(25分)_第2张图片


题目分析

考虑题目是否满足贪心。每次施展魔法会使一段连续的竹子高度变为一半左右的平方根。根据样例,似乎每次让最高的竹子变短就能得到最优解。

假设魔法一次只能对一根竹子使用,永远不出现连续相同高度的竹子,那么显然无论使用魔法的顺序如何,使用次数永远都是固定的;如果会出现连续相同的竹子,先对这排竹子中最高的施展魔法不会对其他竹子的变短次数造成影响。而如果率先对相同高度且不是最高的竹子变短,则可能会丧失更多竹子变成连续的机会,故贪心策略成立。

接下来是如何检测最高竹子和连续相同竹子,如何设计算法是难点。首先想到采用map容器给竹子高度排序,排序过程的复杂度为O(nlogn),不会超时;然后使用深度优先搜索检测最大竹子两个方向上所有相同连续的竹子,让这些竹子一起变短。经计算最大高度的竹子最多经过6次变短高度就能变成1,因此魔法的最多施展次数约为10^6次,如果每次都经过一遍搜索会超时。

为了避免重复检测连续的竹子,对DFS进行改良,每次搜索到连续竹子,在搜索过的竹子上分别添加s\t标记,标志从第s根到第t根的竹子都是相同高度的,从复杂度上将所有相同高度的连续竹子化为一根,差不多是某种程度上的离散化,免去了重复的搜索。


我的代码

long long型数据的范围是9\times 10^{18},可以用来记录竹子的高度。敲了几行代码后发现只用一个dfs很难处理已经搜索过的竹子,因此使用两个dfs,分别向左和向右搜索。

在代码过程中由于不熟悉stl容器语法踩了很多坑:比如迭代器的失效问题;以及multimap.erase(key)会将对应的所有value都删除。

#include 
#include 
#include 
#include 
#include 

using namespace std;
typedef long long ll;
int n; //总竹子个数
ll lenth[200002]; //所有竹子高度
int sign[200002][3]; //sign[n][0]和sign[n][1]分别储存了连续竹子的起点和终点坐标,
//sign[n][2]为连续竹子的的关键坐标(储存在map内)
int other[200002]; //多余不用搜索的竹子
multimap M; //从低到高竹子对应的坐标
multimap::iterator it; //迭代器,用于取出最后一个元素

stack S; //搜索过的竹子
int large; //搜索过的竹子中序号最大的
int small; //搜索过的竹子中序号最小的

/*向右深度优先搜索*/
void dfs1(int n, ll l) { //搜索第n根竹子,长度为l
	n = sign[n][1];
	large = max(large, n); //记录搜索终点
	if (lenth[n + 1] == l) { //向右搜索
		S.push(sign[n + 1][1]); //起点以外搜索过的竹子入栈
		S.push(sign[n + 1][2]); //替代多余的关键坐标
		dfs1(n + 1, l);
	}
}
/*向左深度优先搜索*/
void dfs2(int n, ll l) { //搜索第n根竹子,长度为l
	n = sign[n][0];
	small = min(small, n); //记录搜索起点
	if (lenth[n - 1] == l) { //向左搜索
		S.push(sign[n - 1][0]); //起点以外搜索过的竹子入栈
		S.push(sign[n - 1][2]); //替代多余的关键坐标
		dfs2(n - 1, l);
	}
}
int main() {
	//初始化参数
	for (int i = 0; i <= 200002; i++)
	{
		lenth[i] = 0;
		sign[i][0] = i;
		sign[i][1] = i;
		sign[i][2] = i;
		other[i] = 0;
	}
	//输入总竹子数
	cin >> n;
	//输入竹子高度
	for (int i = 1; i <= n; i++)
	{
		ll L;
		cin >> L;
		lenth[i] = L; //储存在数组
		pair P(L, i); //储存在map容器
		M.insert(P);
	}
	/*循环取出最高竹子*/
	int ans = 0;
	int flag = 0; //循环停止的标志
	while (!flag)
	{
		large = 0;
		small = 200002;
		it = M.end();
		it--;//此时迭代器指向Map最后一个元素
		ll k = (*it).first;
		ll v = (*it).second;//获取键值对
		M.erase(it);//删除当前容器元素
		if (k == 1) {
			flag = 1;
		}
		/*深度优先搜索*/
		if (!flag && other[v] != 1) {
			ans++;//记录搜索次数,即魔法施展次数
			dfs1(v, k);
			dfs2(v, k);
			lenth[v] = floor(sqrt(lenth[v] / 2 + 1));//更新关键竹子高度
			sign[v][0] = small;
			sign[v][1] = large;
			sign[v][2] = v;
			lenth[sign[v][0]] = lenth[v];
			lenth[sign[v][1]] = lenth[v];
			/*出栈操作*/
			while (S.size()) {
				int i = S.top();
				sign[i][0] = small;
				sign[i][1] = large;
				sign[i][2] = v;
				other[i] = 1;
				lenth[i] = lenth[v];
				S.pop();//更新两边竹子的高度,并清除多余的非关键坐标
			}
		}
		/*修改map容器存储*/
		ll nl = floor(sqrt(k / 2 + 1));
		pair P(nl, v);
		M.insert(P);//把新的高度存到Map中
	}
	cout << ans;
	return 0;
}

你可能感兴趣的:(练习日志,算法,c++,学习)