Codeforces Round #696 (Div. 2)D题解析
第一次写CSDN的BLOG,就写第一次打的CF比赛吧!
话不多说,直切正题(加粗部分为核心思路):
题目链接:https://codeforces.com/contest/1474/problem/D
题目大意:有t组数据,每组数据有n坨石头,编号1、2、3…n,各有a1、a2…an块石头,每次可以使编号相邻的两坨石头各拿走一块石头,还有一种超能力可以使相邻两坨石头交换位置(换句话说就是让第i堆石头变为ai+1个,第i+1堆石头变为i个,1<=i<n),问是否可以用一次超能力或者不用超能力使所有石头堆清零,对于每组数据回答“YES”(可以)或“NO”(不可以)
数据范围:1<=t<=1e4,2<=n<=2e5,1<=ai<=1e9,且保证所有数据组的n的和不超过2e5。
题目分析:其实乍一看n的和不超过2e5,这个题就和t关系不大了,所以复杂度基本就是由n决定的,所以可以猜一下这个题复杂度在O(n)~O(nlgn)或(n根号n)之间。但是直接想正确复杂的解法好像不太容易,我们不妨先来思考一下暴力做法。
1、暴力做法
对于每组数据,我们的目的判断石头能否被清空,而又可以发现第一坨石头只能在与第二坨石头操作时清空,那么第一坨清空后,第二坨石头只能在与第三坨石头操作时清空,以此类推。同时,我们可以发现这种思路具有对称性,即第n坨石头只能被第n-1坨石头清空,同上面一样类推。所以,如果不使用超能力的话,我们只需要从前往后推一遍,只要在清空第i-1堆石头后,第i堆石头数目非负,并且清理完成后第n堆石头为空即可。再来考虑超能力,因为超能力只能用一次,且只能用在相邻石头堆之间,那么只要从前到后枚举一下交换位置就可以了(即i从1到n-1,交换第i坨石头和第i+1坨石头),对每次交换之后的n坨石头从前往后检查(和上面不使用超能力一样)即可。这样下来复杂度是O(n2)。
2、优化
暴力与正解之间往往只差了一个优化,所以我们来思考优化思路。起初我想贪心或者剪枝来优化,可是总有办法来hack我自己,所以就老老实实想正解了。前面的暴力复杂度是n2,那么目的就是干掉n*n中的一个n,换句话说,就是少一点枚举交换位置,或者简化检查清空流程。
少一点枚举交换位置?对于n个交换位置,的确有检查交换后奇数和与偶数和是否相等这种剪枝策略,但仍然可以被卡。这个方向无非还是剪枝或者贪心,有风险(其实是我骗不过去hahaha),所以还是换个方向吧。
简化检查清空流程。在上面的检查流程中,我们要做的是从前往后递推,这样就在无意中丢了从后往前也可以的这种对称性。而且在递推的过程中,我们每一次只有在交换的位置才有新的操作,换句话说,我们其余的递推过程都是重复的!那么,对于重复的过程,我们只需要记录一下就可以了。因此有了左记录l[i]和右记录r[i],左记录表示从左向右递推到这个位置时这个位置还剩的石头数,右记录是从右向左递推的剩余石头数。这样对于不用超能力时,每次检查时就只需要检查l[i]=r[i+1]是否成立即可了,如果用超能力,也只需要检查l[i-1],a[i],a[i+1],r[i+2]是否构成可清零的石头堆即可。复杂度大大降低!
但这又产生一个问题,对于l[i],r[i]直接使用,忽视了其可能小于0这一不符合实际的情况,所以还要记录l[i]和r[i]的有效区间。比如在从左向右地推得到l[i]时,如果此时l[i]<0,则对任意的j>=i,l[j]都是无效的了。r[i]同理。所以只需要在使用l[i],r[i]时判断有无超越有效范围即可。
代码如下:
`#include<cstdio>
#define rint register int
using namespace std;
const int mx=200005;
int t,n,lx,rx;
int a[mx],l[mx],r[mx];
bool k;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-48;ch=getchar();}
return x*f;
}
inline bool check(int ax,int bx,int cx,int dx)
{
bx-=ax;cx-=dx;
if(bx<0||cx<0||bx!=cx) return false;
return true;
}
int main()
{
t=read();
for(rint op=1;op<=t;++op){
n=read();lx=n+1;rx=0;
for(rint i=1;i<=n;++i){
a[i]=read();l[i]=a[i]-l[i-1];
if(lx==n+1&&l[i]<0) lx=i;
}
r[n+1]=0;k=false;
for(rint i=n;i>=1;--i){
r[i]=a[i]-r[i+1];
if(rx==0&&r[i]<0) rx=i;
if(r[i]==l[i-1]&&i<=lx&&rx==0){
k=true;break;
}
}
if(k){
printf("YES\n");continue;
}
for(rint i=2;i<=n-2;++i){
if(i>lx) break;
if(i+2<=rx) continue;
if(check(l[i-1],a[i+1],a[i],r[i+2])){
k=true;break;
}
}
if(k) printf("YES\n");
else if(a[2]+r[3]==a[1]&&rx<3) printf("YES\n");
else if(a[n-1]+l[n-2]==a[n]&&lx>n-2) printf("YES\n");
else printf("NO\n");
}
return 0;
}`