P7594 「EZEC-8」Clear Up 题解
BurningEnderDragon · · 题解
题目链接:P7594 「EZEC-8」Clear Up
题面解释
清除方法为:在任意一个位置花费一定能量,清除以当前位置为中心的一个金字塔型区域的方块(在每个位置消除的方块数量沿中心向两边递减)。
Subtask 1
当
当
- 若两个位置上的方块数量均为
0 ,则不需要进行消除,答案为0 ; - 若两个位置上的方块数量相同且不为
0 ,则最优的消除方式为在其中任意一个位置花费相当于( 当前位置上的方块数量+1) 的能量,消除这两个位置上的所有方块,答案为( 任意一个位置上的方块数量+1) ; - 若两个位置上的方块数量不相同,则最优的消除方式为在方块数量较多的位置花费相当于当前位置上的方块数量的能量,消除这两个位置上的所有方块,答案为方块数量较多的位置上的方块数量。
时间复杂度为
代码(题解篇幅有限)
Subtask 2
结论:对于一个不包含
举例说明:
对于以下这组数据:
8
1 2 3 2 1 1 2 1
可以作出以下图像:
如图所示,有以下两种最优的消除方式:
- 在位置
3 消耗3 点能量消除位置1 \sim 3 的方块(红色区域),在位置7 消耗2 点能量消除位置6 \sim 8 的所有方块(绿色区域)。 - 在位置
5 消耗5 点能量消除位置1 \sim 8 的方块(蓝色区域)。
事实上,第二种消除方式可以覆盖位置
对于 Subtask 2,
对于 Subtask 3,则需要以下的优化技巧。
Subtask 3
结论:若当前选择在位置
正确性是显然的。
盗一张 pocafup 的图(
做法为初始化
时间复杂度为
代码(题解篇幅有限)
Subtask 4
结论:
- 对于一个存在
0 的区间,选择一个位置花费能量消除整个区间不一定是最优解。 - 对于两个相邻或相交的区间
A 和B ,它们合并后的区间记为A \bigcup B ,消除区间A ,B ,A \bigcup B 的所有方块的最小花费记为w_A ,w_B ,w_{A \bigcup B} ,若w_{A \bigcup B} \le w_A + w_B ,则可以合并区间A 和B 。
正确性也是显然的。
初始时,可以对每个位置
然后暴力枚举任意两个区间判断是否可以合并,即可得出答案。时间复杂度为
同理,对于 Subtask 5,需要优化合并方式。
Subtask 5
结论:因为每个区间都是金字塔型的,所以对于两个满足
下面这张图片可以说明问题:
可以建立一个栈来进行合并。
做法如下:
- 对于每个位置
i ,若a_i \not= 0 ,则构造一个单独的区间,将其入栈。 - 每构造完一个区间并入栈后,取出栈顶的两个区间,并尝试合并,若可以合并,则删除栈顶的两个区间,并将合并后的区间入栈,直到栈中只有一个区间或者栈顶的两个区间无法合并。
对于两个区间
由于
因为区间个数为
完整AC代码如下:
#include <cstdio>
#include <stack>
inline int Min(const int x,const int y) //据说手写比 STL 快一点
{
return x<=y?x:y;
}
inline int Max(const int x,const int y) //据说手写比 STL 快一点
{
return x>=y?x:y;
}
struct Area //存储一个区间可以覆盖的左右端点
{
int left,right;
};
template <typename type>
class Stack //手写栈,比 STL 常数小,并且操作方便
{
private:
static const int STACK_MAX=1000000;
int head;
type v[STACK_MAX];
public:
Stack() //初始化栈顶指针为 0
{
head=0;
}
inline bool empty() //返回栈是否为空
{
return head==0;
}
inline int size() //返回栈中的元素个数
{
return head;
}
inline void push(const type x) //向栈中插入一个元素
{
v[head++]=x;
return ;
}
inline void pop() //删除栈顶的元素
{
--head;
return ;
}
inline type top(const int x) //返回栈顶的元素
{
return v[head-1-x];
}
};
Stack<Area> st;
int n,a,ans=0;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
scanf("%d",&a); //还是连数组都不用开
if(a)
{
st.push(Area{i-(a-1),i+(a-1)}); //初始时,每个方块个数不为 0 的位置可以构造出一个区间,构造出这个区间并将其入栈
while(st.size()>1 && st.top(1).right>=st.top(0).left-2) //栈内至少有两个区间,且栈顶的两个区间相邻或相交,此时可以合并
{
register int l=Min(st.top(0).left,st.top(1).left),r=Max(st.top(0).right,st.top(1).right);
st.pop(); //栈顶的两个区间出栈
st.pop();
st.push(Area{l,r}); //新区间入栈
}
}
}
while(!st.empty()) //统计答案
{
register int l=st.top(0).left,r=st.top(0).right;
ans+=(r-l+3)/2; //对于每个区间,计算这个区间花费的能量
st.pop();
}
printf("%d\n",ans);
return 0;
}