CF1700C Helping the Nature

题目大意:

给出一个长度为 n 的序列 a,每次可以进行三种操作中的一种:

  • 选择i,将 a_1,a_2,...,a_i减1。
  • 选择i,将 a_i,a_i+1,...,a_n减1。
  • 将所有 a_i加1。
    求最少需要多少次操作将所有 a_i变为0

题解:

一看这道题,就知道是个构造题
一开始也是想了很多方法,比如求一个数与其他数的差值(差一点就到正解了),或者是看绝对值与答案之间的关系(毫无卵用)。但是最后才发现,前两个操作本质上改变的是相邻两个数的差值。
于是有了一个新的思路:将响铃两个数的差值通过一步步变化变为0,并记录一下变为0后每一个数的值,最后进行全体加或减即可得到全0序列;

点击查看代码
#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int MAXN = 200005;
int t,n,a[MAXN],del[MAXN];
signed main(){
	cin >> t;
	while(t--){
		int ans = 0;
		cin >> n;
		memset(a,0,sizeof a);
		for(int i = 1; i <= n; i++){
			cin >> a[i];
			if(i > 1){
				del[i - 1] = a[i] - a[i - 1];//记录前后两个数的差值 
			}
		}
		int now = a[1];//now即你希望把数列中所有数变成的值(在这个程序中我们把now定为当前前缀中每一个数的值),这个值是变化的,在未进行操作时,我们默认将now赋值为数列中第一个元素的值 
		for(int i = 1; i < n; i++){
			if(del[i] > 0){//分两种情况进行讨论,如果后面的数减前一个数大于0,说明后面的数比前面的数大,就应该将后缀全部减小直到差值为0,因为操作的是后缀,跟前缀无关,就不需要更新now了 
				ans += del[i];//答案加上操作的次数,没有问题吧? 
			} 
			else if(del[i] < 0){
				now -= abs(del[i]);//如果后面的数小于前面的数,就需要将前缀减小,直到二者的差值为0,因为处理的是前缀,所以应该将now更新
				ans += abs(del[i]);//更新答案 
			}
		}
		ans += abs(now);//注意,当你把所有相邻的数的差值都变为0时,序列中的数并不一定为0,因此需要再进行一次操作 
		cout << ans << "\n"; 
	}
}
//不用在每次操作时更新所有数,耗时且无意义,只需要记录一下now即可 
posted @ 2022-07-12 20:19  腾云今天首飞了吗  阅读(27)  评论(0编辑  收藏  举报