知识点 - 区间分治(启发式分治) 19HDU多校#10 1011 Make Rounddog Happy 19牛客多校#3 G Removing Stones UVALive-6258 题解
知识点 - 区间分治(启发式分治)
解决问题类型:
要求遍历所有区间。
比如满足某性质的区间个数、是否每个区间都满足某个性质。
想法是:分治时,对于枚举计算答案的过程进行一些操作,(一般为选小的一边枚举),避免退化为O(n2)O(n^2)O(n2)
算法:
referrence
先找到分界限,枚举少的一边,然后两边递归。
int get(int L,int R)//ST表get最大值 { int k=lg[R-L+1]; return a[st[L][k]]>=a[st[R-(1<<k)+1][k]]?st[L][k]:st[R-(1<<k)+1][k]; } void solve(int L,int R) { if(L>R) return ; int pos=get(L,R); if(pos-L<R-pos){//枚举少的一边 rep(i,L,pos){//O(n) or O(nlogn)都能接受 int t=a[pos]-K,lR=i+t-1; int fcy=min(R,B[i]); lR=max(lR,pos); if(lR>fcy) continue; ans+=fcy-lR+1; } } else { rep(i,pos,R){ int t=a[pos]-K,rL=i-t+1; int fcy=max(L,A[i]); rL=min(rL,pos); if(rL<fcy) continue; ans+=rL-fcy+1; } } solve(L,pos-1); solve(pos+1,R); }
例题
UVALive-6258 Non-boring sequences
题意
称所有区间都有一个数只出现一次的数列为Non-boring的。
给你一个数列,问是不是Non-boring的
分析
区间分治,把只出现一次的那个数作为分界线,两边递归地判断。
细节
如何找只出现一次的数?
首先预处理出每个数之前之后第一次出现的位置lst[i],nxt[i]lst[i],nxt[i]lst[i],nxt[i]。于是可以用
lst[l]<L&&nxt[l]>RO(1)判断在(l,r)(l,r)(l,r)区间是否只出现一次。
如果每次都for一遍找,最坏情况下,每次都扫到了最后一个才找到,此时的复杂度为:
T(n)=T(n−1)+O(n)
T(n)=T(n-1)+O(n)
T(n)=T(n−1)+O(n)
如何避免退化为O(n2)O(n^2)O(n2)?
我们从区间左右端点同时往中间扫。这样就跟保证了:
T(n)=2T(n/2)+O(n)
T(n)=2T(n/2)+O(n)
T(n)=2T(n/2)+O(n)
推导并不严格,感性上理解,对于卡L=n-2的样例,复杂度为T(n)=T(n−2)+O(1)T(n)=T(n-2)+O(1)T(n)=T(n−2)+O(1) 变成了O(n).
#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 = 200010; int a[maxn], lst[maxn], nxt[maxn]; map<int, int>mp; bool check(int L, int R) { if (L >= R) return true; int l = L, r = R; rep(i, L, R) { if (i & 1) { if (lst[l]<L&&nxt[l]>R) return check(L, l - 1) && check(l + 1, R); l++; } else { if (lst[r]<L&&nxt[r]>R) return check(L, r - 1) && check(r + 1, R); r--; } } return false; } int main() { int T, N; scanf("%d", &T); while (T--) { mp.clear(); scanf("%d", &N); rep(i, 1, N) scanf("%d", &a[i]); rep(i, 1, N) { nxt[mp[a[i]]] = i; lst[i] = mp[a[i]]; mp[a[i]] = i; } rep(i, 1, N) nxt[mp[a[i]]] = N + 1; if (check(1, N)) puts("non-boring"); else puts("boring"); } return 0; }
19牛客多校#3 G Removing Stones
题意
给你一个数组,问满足区间最大值大于等于区间总和的1/2(区间长度大于2)的区间个数
分析
区间分治,把最大值作为分界线,计算经过该点的区间个数,然后两边递归做。
细节
如何rmq?最大值用ST表O(1)O(1)O(1)查询。
如何统计过某点的区间个数?
枚举一端,另外一段二分即可。(区间和关于右端点严格递增。)不妨设每次枚举左区间,复杂度为O(LlogR)O(L\log R)O(LlogR)(其中LR为左右区间长度)。最坏情况下L=n−2,R=1L=n-2,R=1L=n−2,R=1(其中n为区间),那么复杂度为
T(n)=T(n−2)+O(n−2)
T(n)=T(n-2)+O(n-2)
T(n)=T(n−2)+O(n−2)
如何避免退化为O(n2)O(n^2)O(n2)?
我们每次选短的那一段枚举,最坏情况下L=n/2,R=n/2L=n/2,R=n/2L=n/2,R=n/2
T(n)=2T(n/2)+O(n2logn2)
T(n)=2T(n/2)+O(\frac{n}{2}log\frac{n}{2})
T(n)=2T(n/2)+O(2nlog2n)
严格复杂度不是这么推的,但最终结论是:均摊复杂度上界为O(nlog2n)O(nlog^2n)O(nlog2n)
感性上理解,如果L=n-2,那么T(n)=T(n−2)+O(log(n−2))T(n)=T(n-2)+O(log(n-2))T(n)=T(n−2)+O(log(n−2))还是O(nlogn)O(nlogn)O(nlogn)
#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; }
19HDU多校#10 1011 Make Rounddog Happy
题意:
称满足max(al,al+1,…,ar)−(r−l+1)≤k\max({a_l, a_{l+1}, \ldots, a_r}) - (r - l + 1) \leq kmax(al,al+1,…,ar)−(r−l+1)≤k的且区间内数两两不同的区间为happy的。
问有几个happy区间?
分析:
区间分治,将区间最大值作为分界线,计算过这个点的区间数,然后两边递归。
细节:
如何统计过这个点的区间数?
首先ST表找到这个点a[pos]=max(al,al+1,…,ar)a[pos]=\max({a_l, a_{l+1}, \ldots, a_r})a[pos]=max(al,al+1,…,ar),移项得到:
(r−l+1)≥a[pos]−kr≥a[pos]−k+l−1
(r - l + 1) \ge a[pos]-k\\
r \ge a[pos]-k+ l - 1
(r−l+1)≥a[pos]−kr≥a[pos]−k+l−1
问题变成对每个a[pos],枚举它左边的每个点作为l,算出右边所有r≥a[pos]−k+l−1r \ge a[pos]-k+ l - 1r≥a[pos]−k+l−1且区间内没有重复的r的数量。可以直接O(1)算出,实现代码:(注意边界)
rep(i, L, pos) { int lR = i + a[pos] - K - 1; int fcy = min(R, B[i]); lR = max(lR, pos); if (lR>fcy) continue; ans += fcy - lR + 1; }
如何保证区间内没有重复?
O(n)预处理每个a[i]a[i]a[i]只考虑不重复的条件,向前向后影响的区间范围A[i],B[i]A[i],B[i]A[i],B[i]。具体来说,A代表a[i]a[i]a[i]上一次出现的位置,但如果某一个数jjj在A[i],iA[i],iA[i],i之间出现了两次,就用A[j]A[j]A[j]更新A[i]A[i]A[i] 。具体实现:(resp. B)
rep(i,1,N) vis[i]=0; A[1]=1; vis[a[1]]=1; rep(i,2,N){ if(vis[a[i]]) A[i]=max(A[i-1],vis[a[i]]+1); else A[i]=A[i-1]; vis[a[i]]=i; }
如何防止退化到O(n2)O(n^2)O(n2)?
按照上面的套路,每次枚举较小的边即可,复杂度为:
T(n)=2T(n/2)+O(n)
T(n)=2T(n/2)+O(n)
T(n)=2T(n/2)+O(n)
#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=1000010; int a[maxn],st[maxn][21],lg[maxn],K,N; ll ans; int A[maxn],B[maxn],vis[maxn]; int get(int L,int R) { int k=lg[R-L+1]; return a[st[L][k]]>=a[st[R-(1<<k)+1][k]]?st[L][k]:st[R-(1<<k)+1][k]; } void solve(int L,int R) { if(L>R) return ; int pos=get(L,R); if(pos-L<R-pos){ rep(i,L,pos){ int t=a[pos]-K,lR=i+t-1; int fcy=min(R,B[i]); lR=max(lR,pos); if(lR>fcy) continue; ans+=fcy-lR+1; } } else { rep(i,pos,R){ int t=a[pos]-K,rL=i-t+1; int fcy=max(L,A[i]); rL=min(rL,pos); if(rL<fcy) continue; ans+=rL-fcy+1; } } solve(L,pos-1); solve(pos+1,R); } int main() { int T; lg[0]=-1; rep(i,1,maxn-1) lg[i]=lg[i>>1]+1; scanf("%d",&T); while(T--){ scanf("%d%d",&N,&K); ans=0; rep(i,1,N) scanf("%d",&a[i]); rep(i,1,N) st[i][0]=i; rep(i,1,20) { rep(j,1,N+1-(1<<i)) st[j][i]=a[st[j][i-1]]>=a[st[j+(1<<(i-1))][i-1]]?st[j][i-1]:st[j+(1<<(i-1))][i-1]; } rep(i,1,N) vis[i]=0; A[1]=1; vis[a[1]]=1; rep(i,2,N){ if(vis[a[i]]) A[i]=max(A[i-1],vis[a[i]]+1); else A[i]=A[i-1]; vis[a[i]]=i; }rep(i,1,N) vis[i]=0; B[N]=N; vis[a[N]]=N; for(int i=N-1;i>=1;i--){ if(vis[a[i]]) B[i]=min(B[i+1],vis[a[i]]-1); else B[i]=B[i+1]; vis[a[i]]=i; } solve(1,N); printf("%lld\n",ans); } return 0; }
- 启发式分治:2019牛客多校第三场 G题 Removing Stones
- UVALive 6258 Non-boring sequences 分治
- hdu 5412 CRB and Queries 2015多校联合训练赛#10 分治 求区间第k大数
- uva live 3516 Exploring Pyramids 区间DP
- HDU 5700 区间交 百度之星题解 round2B (set+vector)
- UVaLive5059 Playing With Stones
- UVALive 6838 Flipping Parentheses // 线段树 区间修改 最值查询
- UVALive 3516 Exploring Pyramids 区间dp+计数原理
- 【HDU5808 BestCoder Round 86E】【时间排序 树状数组计数 bitsetDP 或分治】Price List Strike Back 距离范围、区间范围商店购物 使得价值和恰为m
- UVALive 5695 -The Last Puzzle -区间dp
- UVALIVE 2519 Radar Installation 区间选点问题
- UVALive 2519 Radar Installation 雷达扫描 区间选点问题
- UVALive 6084 Happy Camper(数学题)
- UVALive 3523 Knights of the Round Table(BCC+二分图)
- 牛客多校第四场——splay的区间平移操作
- UVALive 4857 Halloween Costumes(区间DP)
- UVALive - 4108 SKYLINE 线段树(区间修改)
- UVALive 4108 SKYLINE(线段树区间更新)
- UVALive - 3523 Knights of the Round Table(双联通分量)
- uvalive4394(区间dp)