2019牛客暑期多校训练营(第三场)G: Removing Stones(启发式分治)
题意:给定N,表示N堆石子,每堆石子数为a[],问多少个区间,可以满足“石子总和若为偶数,那么可以两两取来自不同堆的石子,直到取完; 如果为奇数,那么排除其中一个,然后可以两两取来自不同堆的石子,直到取完”。
思路:结论是,如果一个区间的区间和大于等于区间最大值的两倍,则这个区间合法。 考虑分治,我们首先找到区间最大值(为了不重复统计,多个最大值时,统一取最左边的,这个可以ST表示实现),然后考虑跨越这个位置的合法区间个数。枚举一端,另外一段二分即可。
由于分治的性质,我们每次的复杂度要倾向于小的那边,即是一个启发式合并的逆过程,所以启发式分治复杂度是O(NlogN)的,加上二分,这个做法的复杂度是O(Nlog^2N)。
可以参考差不多的题:https://www.cnblogs.com/hua-dong/p/11171241.html。所以我感觉我遇到的原题还挺多的。
#include<bits/stdc++.h> #define ll long long #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; const int maxn=300010; int lg[maxn],a[maxn],dp[maxn][20],N; ll sum[maxn],sum2[maxn],ans; void RMQ() { rep(i,1,N) dp[i][0]=i; for(int i=1;(1<<i)<=N;i++){ for(int j=1;j+(1<<i)-1<=N;j++){ dp[j][i]=a[dp[j][i-1]]>=a[dp[j+(1<<(i-1))][i-1]]? dp[j][i-1]:dp[j+(1<<(i-1))][i-1]; } } } void solve(int L,int R) { if(L>=R) return ; int k=lg[R-L+1]; int Mid=(a[dp[L][k]]>=a[dp[R-(1<<k)+1][k]]? dp[L][k]:dp[R-(1<<k)+1][k]); if(Mid-L<R-Mid){ rep(i,L,Mid) { int pos=lower_bound(sum+Mid,sum+R+1,sum[i-1]+2LL*a[Mid])-sum; ans+=R-pos+1; } } else { rep(i,Mid,R) { int pos=lower_bound(sum2+N-Mid+1,sum2+N-L+2,sum2[N-i]+2LL*a[Mid])-sum2; ans+=N+1-L-pos+1; } } solve(L,Mid-1); solve(Mid+1,R); } int main() { lg[0]=-1; rep(i,1,maxn-1) lg[i]=lg[i>>1]+1; int T; scanf("%d",&T); while(T--){ scanf("%d",&N); ans=0; rep(i,1,N) scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i]; rep(i,1,N) sum2[i]=sum2[i-1]+a[N+1-i]; RMQ(); solve(1,N); printf("%lld\n",ans); } return 0; }
It is your time to fight!