Codeforces696Div2-D - Cleaning:思维,前后缀

前言:

又没搞出来CF的思维题,拉了

题目大意:

给你 n n n堆石子 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an,你可以进行若干次以下操作:选择第 i i i i + 1 i+1 i+1堆石子,从中同时各移除一个石子(前提是第 i i i i + 1 i+1 i+1堆石子都不为空).

然后你可以交换一次任意两个相邻的石头堆 a i , a i + 1 a_i,a_{i+1} ai,ai+1。问你是否能够移除所有石头.
n ≤ 2 e 5 , a i ≤ 1 e 9 n \leq 2e5,a_i \leq 1e9 n2e5,ai1e9

题目思路:

根据题目大意,我们容易发现:
1.想要最终移除完所有石子,那么对 a 1 a_1 a1的操作次数是固定的,即必须将 a 1 a_1 a1拿到0.那么 a 2 = a 2 − a 1 a_2 = a_2 - a_1 a2=a2a1.
问题转化为子问题:拿完 [ 2 , n ] [2,n] [2,n]范围内所有的石头.递归操作下去我们发现其实操作方法是固定了的.

所以若没有交换的操作,我们很容易在线性时间内 c h e c k check check移除合法性.

上述结论也可以反着做: a n a_n an的操作次数是固定的,然后逆推也可以.所以其实这个过程消除的顺序不重要.

2.考虑可以交换的情况.最朴素的方法就是:交换完之后,从前往后消除到第 i − 2 i-2 i2堆石头,然后从后往前消除到第 i + 2 i+2 i+2堆石头,然后再 c h e c k check check剩下四堆石子的合法性.但是时间复杂度不够优秀.

所以我们考虑正着维护一个数组: p [ i ] p[i] p[i]代表消除石子堆直至第i堆时,第 i i i堆石子剩下的石子数.
反着维护一个数组: s [ i ] s[i] s[i]代表从后往前消除石子堆直至第i堆时,第 i i i堆石子剩下的石子数.

有了这两个数组,我们就可以将上述算法优化到 O ( n ) O(n) O(n)了.

所以做法就是:先判不用交换时是否能够被消除.
之后我们枚举每一个相邻的石堆,去 c h e c k check check石头堆 { p i − 2 , a i + 1 , a i , s i + 2 } \{p_{i-2},a_{i+1},a_{i},s_{i+2}\} {pi2,ai+1,ai,si+2}是否能够被消除.

然后为了统一算法,我们在序列头尾各增加一个含有 0 0 0个石头的石头堆.

AC代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 1e6 + 5;
const int mod = 1e9 + 7;
ll a[maxn] , p[maxn] , s[maxn];
int main()
{
    ios::sync_with_stdio(false);
    int t; cin >> t;
    while (t--){
        int n ; cin >> n;
        for (int i = 1 ; i <= n ; i++) cin >> a[i];
        p[0] = 0;
        for (int i = 1 ; i <= n ; i++) {
            if (p[i - 1] == -1) p[i] = -1;
            else p[i] = max( -1ll , a[i] - p[i - 1] );
        }
        s[n + 1] = 0;
        for (int i = n ; i >= 1 ; i--) {
            if (s[i + 1] == -1) s[i] = -1;
            else s[i] = max( -1ll , a[i] - s[i + 1] );
        }
        bool ok = true;
        for (int i = 1 ; i < n ; i++) if (p[i] == -1) ok = false;
        if (p[n]) ok = false;
        if (ok) {
            cout << "YES" << endl;
            continue;
        }
        for (int i = 1 ; i < n && !ok; i++) {
            if (p[i - 1] == -1 || s[i + 2] == -1) continue;
            ll tmp[4] = {p[i - 1] , a[i + 1] , a[i] , s[i + 2]};
            for (int j = 1 ; j <= 3 ; j++) {
                if (tmp[j - 1] == -1) tmp[j] = -1;
                else tmp[j] = max (-1ll , tmp[j] - tmp[j - 1]);
            }
            ok |= (tmp[1] >= 0 && tmp[2] >= 0 && tmp[3] == 0);
        }
        cout << (ok ? "YES" : "NO") << endl;
    }
    return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值