Codeforces Round #696 (Div. 2) D. Cleaning (思维,前缀和)

  • 题意:有一堆石子,你每次可以选择相邻(就算两堆石子中间有很多空堆也不算)的两堆石子,使得两堆石子的个数同时(-1),你在刚开始的时候有一次交换相邻石子的机会,问你最后能否拿走所有石子.
  • 题解:对于第一堆石子和最后一堆石子,它们只能靠第二堆石子和倒数第二堆石子减去才合法,所以我们由第一堆石子不断向右推和最后一堆石子不断向左推,这个过程可以用前缀和(pre)与后缀和(suf)表示.如果我们当前选择堆(<i-1,i>)的话,那么前缀和(pre[i-2])和后缀和(suf[i+1])是不会受到任何影响的,如果问题出在我们当前选择的这个堆上的话,我们就必须保证(pre[i-2])(suf[i+1])都是合法的,同时,我们将(pre[i-2])(a[i-1])合并后和(suf[i+1])(a[i])合并后的值应该是合法且相等的.于是乎,我们可以枚举所有堆,然后判断不反转和反转是否合法.这里注意,如果(pre[i])不合法,我们将(pre[i]=-1),那么(i)后面的(pre)也都是不合法的,都标记为(-1),(suf)同理.
  • 代码:
#include <bits/stdc++.h>
#define ll long long
#define fi first
#define se second
#define pb push_back
#define me memset
#define rep(a,b,c) for(int a=b;a<=c;++a)
#define per(a,b,c) for(int a=b;a>=c;--a)
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
using namespace std;
typedef pair<int,int> PII;
typedef pair<ll,ll> PLL;
ll gcd(ll a,ll b) {return b?gcd(b,a%b):a;}
ll lcm(ll a,ll b) {return a/gcd(a,b)*b;}

int _;
int a[N];
int pre[N],suf[N];

bool check(int i){
	if(pre[i-2]!=-1 && suf[i+1]!=-1 && a[i-1]>=pre[i-2] && a[i]>=suf[i+1] && a[i-1]-pre[i-2]==a[i]-suf[i+1])
		return true;
	return false;
}

int main() {
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);

	cin>>_;
	while(_--){
		int n;
		cin>>n;
		rep(i,1,n) cin>>a[i];

		pre[0]=0,suf[n+1]=0;

		rep(i,1,n){
			if(pre[i-1]==-1 || a[i]<pre[i-1]){
				pre[i]=-1;
			}else{
				pre[i]=a[i]-pre[i-1];
			}
		}

		per(i,n,1){
			if(suf[i+1]==-1 || a[i]<suf[i+1]){
				suf[i]=-1;
			}else{
				suf[i]=a[i]-suf[i+1];
			}
		}

		bool flag=false;

		rep(i,2,n){
			if(check(i)) {flag=true;break;}
			swap(a[i],a[i-1]);
			if(check(i)) {flag=true;break;}
			swap(a[i],a[i-1]);
			if(pre[i-1]==-1) break; //小小的优化
		}

		if(flag) cout<<"YES
";
		else cout<<"NO
";
	}

    return 0;
}
原文地址:https://www.cnblogs.com/lr599909928/p/14342781.html