您的位置:首页 > 移动开发

知识点 - 区间分治(启发式分治) 19HDU多校#10 1011 Make Rounddog Happy 19牛客多校#3 G Removing Stones UVALive-6258 题解

2019-08-21 21:20 1536 查看
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/qq_41848675/article/details/99999209

知识点 - 区间分治(启发式分治)

解决问题类型:

要求遍历所有区间。

比如满足某性质的区间个数、是否每个区间都满足某个性质。

想法是:分治时,对于枚举计算答案的过程进行一些操作,(一般为选小的一边枚举),避免退化为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]>R
O(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(Llog⁡R)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(2n​log2n​)
严格复杂度不是这么推的,但最终结论是:均摊复杂度上界为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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: