Codeforces Round 933 (Div. 3) B.Rudolf and 121(贪心)

Rudolf 有一个由 n 个整数组成的数组 a ,元素的编号从 1 到 n 。

在一次操作中,他可以选择索引 i ( 2≤i≤n−1 ) 并赋值:

  • a i − 1 = a i − 1 − 1 a_{i−1} = a_{i−1} −1 ai1=ai11
  • a i = a i − 2 a_i = a_i−2 ai=ai2
  • a i + 1 = a i + 1 − 1 a_{i+1} = a_{i+1}−1 ai+1=ai+11

鲁道夫可以任意多次使用这个运算。任何索引 i i i 都可以使用 0 0 0 次或更多次。

他能用这个运算使数组中的所有元素都等于零吗?

输入
输入的第一行包含一个整数 t ( 1 ≤ t ≤ 1 0 4 ) t ( 1≤t≤10^4 ) t(1t104) - 测试中的测试用例数。

每个案例的第一行包含一个整数 n ( 3 ≤ n ≤ 2 ⋅ 1 0 5 ) n ( 3≤n≤2⋅10^5 ) n(3n2105)- 数组中的元素个数。

每个案例的第二行包含 n n n 个整数 a 1 , a 2 , … , a n ( 0 ≤ a j ≤ 1 0 9 ) a_1,a_2,…,a_n ( 0≤a_j≤10^9 ) a1,a2,,an(0aj109) - 数组中的元素

保证所有测试用例中 n n n 的值之和不超过 2 ⋅ 1 0 5 2⋅10^5 2105 。 输出

对于每个测试用例,如果可以通过所述操作使数组的所有元素为零,则输出 “YES”。否则,输出 “NO”。

每个字母可以以任何大小写(小写或大写)输出。例如,字符串 “yEs”、“yes”、"Yes "和 "YES "将被视为肯定答案。

Example
input

7
5
1 3 5 5 2
5
2 4 4 5 1
5
0 1 3 3 1
6
5 6 0 2 3 0
4
1 2 7 2
3
7 1 0
4
1 1 1 1

output

YES
NO
YES
NO
NO
NO
NO

记录此题以提醒自己:贪心的代码大部分时间都会是 O ( N ) O(N) O(N扫过去一遍,不会再回头或是来回横跳,要以这个作为贪心策略的依据之一,如果你写了一个while循环或是乱七八糟的多重循环,你就要怀疑你的贪心策略是不是出问题了。

简化一下此题的描述便是:给定一个长度为 n n n的序列 a a a,进行没有限制次数的操作,每次能够让当前下标的元素减 2 2 2,左右的元素减 1 1 1,问能否有一种做法使得序列全部变为 0 0 0

首先:操作仅能够在 2 2 2 ~ n − 1 n-1 n1的下标位置进行(如果起始下标为 1 1 1),不然的话会越界,所以最终枚举的时候就直接枚举 2 2 2 ~ n − 1 n-1 n1

然后:怎么贪心才能够使得最终的结果尽量接近全 0 0 0 ? 如果让你从头到尾只能扫一遍而不能回头看,那么最好的贪心策略就是尽量减,能减多少减多少,并且同时要保证尽量不把任何一个数减成负的,因为如果有一个变成负的了,之后再怎么操作也没用了。

那怎么尽量减呢:扫 2 2 2 ~ n − 1 n-1 n1,对于每个元素首先取前一个元素和当前元素除 2 2 2的最小值为 t t t,然后让前一个元素减去 t t t,当前个元素减去 2 t 2t 2t,后一个元素减去 t t t,这样就能最大限度保证不会变为负数,如果在这种情况下还会导致某个元素变为负的,或者是前一个元素减不成 0 0 0,那这个序列就一定没办法变为全 0 0 0了。在这里我们并不需要提前考虑后一个元素,因为后一个元素还会被后边元素影响,所以按照贪心的观念我们不去多思考,直接着手于眼前。

到最后还要特判一下没有在循环里判断的数。

代码:

#include<iostream>
using namespace std;
const int N = 2e5 + 10;

int a[N]; int n;

int main() {
	int t; cin >> t;
	while (t--) {
		cin >> n;
		for (int i = 1; i <= n; i++)cin >> a[i];

		bool has_answer = 1;

		for (int i = 2; i <= n - 1; i++) {
			int sub = min(a[i - 1], a[i] / 2);
			a[i - 1] -= sub;
			a[i] -= 2 * sub;
			a[i + 1] -= sub;

			if (a[i-1] > 0 || a[i - 1] < 0 || a[i] < 0 || a[i + 1] < 0) {
				has_answer = 0;
				break;
			}
		}
		if (a[n - 1] != 0 || a[n] != 0)has_answer = 0;

		if (has_answer)cout << "YES\n";
		else cout << "NO\n";
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值