题目链接:https://ac.nowcoder.com/acm/contest/883/G
题目大意:有\(n\)堆石头,每堆有\(a_i\)个,每次可以选其中两堆非零的石堆,各取走一个石子,当所有石堆的石子数均为\(0\)时获胜。问有多少个区间\([l,r]\)可以保证获胜(若区间内石子数总和为奇数则会选一堆石子数最小的石堆取走一个石头)。
题解:显然,对其中的一个区间,如果区间内的最大值\(mx\)不超过其总和\(sum\)的一半,则能保证获胜。于是可以考虑每一个\(a_i\)作为最大值的区间\([l_i,r_i]\),在答案中减去其中不满足要求的区间即可,注意这个区间一定要包含\(i\)。
每次枚举\(i\)时,先二分出最左边的\(l\)使得区间\([l,i]\)恰好不满足条件,然后再二分出最左边的\(r\)使得\([l,r]\)恰好不满足条件,每次将\(l\)加一直至达到当前的\(i\)即可
时间复杂度为O(能过),求大佬帮忙分析复杂度orz
#includeusing namespace std; #define N 300001 #define LL long long LL T,n,a[N],l[N],r[N],s[N],f[N],ans; void rua(LL cur,LL L,LL R) { LL l,r; l=upper_bound(s+L-1,s+cur+1,s[cur-1]-a[cur])-s;l++; r=cur; while(l<=cur) { r=min(R,(LL)(lower_bound(s+r+1,s+R+1,2ll*a[cur]+s[l-1])-s-1)); ans-=r-max(cur,max(f[l]+1,l))+1; f[l]=max(f[l],r); l++; } } void init() { scanf("%lld",&n); for(LL i=1;i<=n;i++) scanf("%lld",&a[i]),s[i]=s[i-1]+a[i],f[i]=i; l[1]=1,r[n]=n; for(LL i=2;i<=n;i++) { LL _=i; while(_>1 && a[i]>=a[_-1]) _=l[_-1]; l[i]=_; } for(LL i=n-1;i>=1;i--) { LL _=i; while(_ a[_+1]) _=r[_+1]; r[i]=_; } ans=n*(n-1)/2; for(LL i=1;i<=n;i++) rua(i,l[i],r[i]); printf("%lld\n",ans); } int main() { //freopen("test.in","r",stdin); scanf("%lld",&T); while(T--)init(); return 0; }