减法操作

减法操作

给定一个包含 $n$ 个非负整数的数列 $a_1,a_2, \dots ,a_n$。

你可以对该数列进行以下两种减法操作:

  • 任选其中一个元素,并将该元素的值减去 $2$。
  • 任选两个相邻元素,并将两个元素的值各减去 $1$。

请你判断,能否经过一系列减法操作,使得数列中的所有元素都变为 $0$。

输入格式

第一行包含整数 $n$。

第二行包含 $n$ 个非负整数 $a_1,a_2, \dots ,a_n$。

输出格式

如果能够经过一系列减法操作,使得数列中的所有元素都变为 $0$,则输出 YES ,否则输出 NO 。

数据范围

前 $6$ 个测试点满足 $1 \leq n \leq 10$。
所有测试点满足 $1 \leq n \leq 2 \times {10}^{5}$,$0 \leq a_i \leq {10}^{4}$。

输入样例1:

4
1 2 1 2

输出样例1:

YES

输入样例2:

3
1 0 1

输出样例2:

NO

 

解题思路

  比赛的时候想到的思路是,既然要求最后整个序列的每个数都变成$0$,那么就构造出一种方案,看看能不能让每个位置上的数都变成$0$。从前往后遍历每一个数,如果这个数是偶数,那么只进行操作$2$就可以。如果这个数是奇数,那么至少要执行一次操作$1$,这里就规定每次执行操作$1$时都是与当前位置的下一个位置减$1$,且对该数只执行一次操作$1$,剩下的都执行操作$2$(如果是序列的最后一个数,由于没有下一位了,因此如果最后一个数是奇数,那么就不可以执行任何操作了,即无法将序列变成全$0$)。最后看一下每一个位置是否都是非负数并且都是偶数就可以了。

  补一下y总严格的证明。

  我们将所有的操作方案看作是一个集合,现在考虑一下最优解有什么性质,看能不能在一个子集中找到答案。首先操作顺序并不会影响结果,比如对于一个数,先减$2$再减$1$,或者是先减$1$再减$2$都不会影响这个数的最终结果。因此我们可以人为定义一个操作顺序,就是先执行所有的操作$2$再去执行操作$1$,我们就可以在这个子集中看一下是否存在一种方案使得序列变成$0$。同时对于相邻的两个数,如果这两个数执行了两次操作$2$,那么等价于这两个数分别执行一次操作$1$,因此在这个子集中我们还可以考虑另外一个子集,在这子集中同一对元素只会执行一次操作$2$,因此最终我们只需要在这个子集的子集里面考虑就可以了,如果这个子集中存在某个方案使得序列变成$0$,那么代价于执行完所有的操作$2$后所有元素都变成非负偶数才可以。

  我们可以从前往后找到第一个奇数,由于要变成偶数,那么就要对这个数执行操作$2$,有两种选择,要么与这个数后面一个数执行操作$2$,要么与这个数前面一个数执行操作$2$。如果是与这个数前面一个数执行操作$2$,由于前面这个数是偶数,因此操作完后就变成奇数,由于要变成偶数,因此也有两种选择,与后面一个数或者与前面一个数执行操作$2$。如果是选择后面一个数,那么相当于相邻的两个元素执行了两次操作$2$,而这种方案不在这个子集中,因此只能选择前面的数。以此类推,前面的数又从偶数变成奇数了,那么总是与前一个数执行操作$2$,直到是序列的第一个元素,第一个数变成奇数,此时它前面没有元素了,就无法执行操作$2$,也不能与后面一个数执行操作$2$,因此就无解了。因此我们从前往后枚举找到第一个奇数后,我们只能操作这个数和它后面一个数,所以操作方案是唯一的。

  如果从前往后枚举找到某个数是偶数,要么不执行操作$2$,要么执行两次操作$2$,并且这两次操作$2$分别是选择前面一个数和后面一个数。而与前面一个数执行完操作$2$后,前面那个数就变成奇数了,那么又要对前面这个数执行操作$2$,可以发现又变成上面所描述的无解的情况了。因此如果遇到偶数,我们不能进行操作$2$。

  因此如果遇到奇数我们只能操作这个数和它后面一个数,如果数偶数我们不能操作。所以操作方案是唯一确定的。

  AC代码如下:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N = 2e5 + 10;
 5 
 6 int a[N];
 7 
 8 int main() {
 9     int n;
10     scanf("%d", &n);
11     for (int i = 0; i < n; i++) {
12         scanf("%d", a + i);
13     }
14     
15     bool flag = true;
16     for (int i = 0; i < n; i++) {
17         if (a[i] < 0) { // 每个数要求是非负偶数
18             flag = false;
19             break;
20         }
21         if (a[i] & 1) { // 奇数
22             if (i == n - 1) flag = false;   // 最后一个数是奇数,而最后一个数后面没有数,无法执行操作2
23             a[i + 1]--; // 与后面一个数执行操作2
24         }
25     }
26     
27     printf("%s", flag ? "YES" : "NO");
28     
29     return 0;
30 }

 

参考资料

  AcWing 4619. 减法操作(AcWing杯 - 周赛):https://www.acwing.com/video/4405/

posted @ 2022-09-25 14:05  onlyblues  阅读(57)  评论(0编辑  收藏  举报
Web Analytics