启发式分治

最近跟着这个大佬 nimphy 学了一下启发式分治

根据目前对启发式分治的理解写一下总结

启发式分治,一般用于解决区间问题,对满足某种条件的区间进行分治,在枚举其分治点的时候进行一些启发式操作

例题一
Non-boring sequences
题目大意:
对一个序列,如果其任意子区间都有至少一个数只出现一次,那么则称这个序列为non-boring的,否则为boring

思路:
预处理每个数上一次出现的位置以及下一次出现的位置
对一个区间,如果有一个数满足条件,则以这个数为分界点,对两边进行分治
在枚举满足条件的分界点时从区间两边向中间进行枚举
使本身可能成为On的算法优化到Onlogn
大致证明可由 T(n) = 2 * T(n/2) + O(n) 得到

代码

#include <bits/stdc++.h>
using namespace std;

const int maxn=2e5+5;
int l[maxn],r[maxn];
int a[maxn];
unordered_map<int,int> m;

bool solve(int L,int R){
    if(L>=R) return true;
    int ll=L,rr=R;
    for(int i=1;i<=R-L+1;i++){
        if(i&1){
            if(l[ll]<L && r[ll]>R)
                return solve(L,ll-1) && solve(ll+1,R);
            ll++;
        }
        else{
            if(l[rr]<L && r[rr]>R)
                return solve(L,rr-1) && solve(rr+1,R);
            rr--;
        }
    }
    return false;
}

int main(){
    int T;
    cin>>T;
    while(T--){
        int n;
        cin>>n;
        for(int i=1;i<=n;i++) cin>>a[i];
        m.clear();
        for(int i=1;i<=n;i++){
            l[i]=m[a[i]];
            m[a[i]]=i;
        }
        m.clear();
        for(int i=n;i>=1;i--){
            if(m[a[i]]==0) r[i]=n+1;
            else r[i]=m[a[i]];
            m[a[i]]=i;
        }
        if(solve(1,n)) cout<<"non-boring"<<endl;
        else cout<<"boring"<<endl;
    }
}

例题二
hdu6701 杭电多校第10场1011 Make Rounddog Happy
题目大意:
给一个序列a,找到满足条件的所有子区间数量
条件为 max(al, al+1, … , ar) − (r − l + 1) ≤ k 且 区间内每个数字只出现一次

思路:
对区间的最大值进行分治,对于一个区间,区间左端点为L右端点为R,首先找到区间最大值的位置pos作为分界点
对较小的那一段进行枚举,作为结果区间的一个端点,通过预处理O1找到满足条件的区间另一端点的范围
然后对分界点两端的区间继续进行分治
因为每次枚举的都是较小的一段,可保证复杂度仍在nlogn级别内

代码:

#include<bits/stdc++.h>
using namespace std;

const int maxn=3e5+5;
int a[maxn],A[maxn],B[maxn],cnt[maxn];
long long ans=0;
int n,k;

class segment_tree{public:
#define nd node[now]
#define ndl node[now*2]
#define ndr node[now*2+1]
    struct segment_node{
        int l,r,maxx;
    }node[maxn*4];

    void pushup(int now){
        nd.maxx=max(ndl.maxx,ndr.maxx);
    }

    void maketree(int s,int t,int now=1){
        nd=(segment_node){s,t,0};
        if(s==t){
            nd.maxx=a[s];
            return;
        }
        maketree(s,(s+t)/2,now*2);
        maketree((s+t)/2+1,t,now*2+1);
        pushup(now);
    }

    int query(int s,int t,int now=1){
        if(s<=nd.l && t>=nd.r){
            return nd.maxx;
        }
        int maxx=0;
        if(s<=ndl.r) maxx=max(maxx,query(s,t,now*2));
        if(t>=ndr.l) maxx=max(maxx,query(s,t,now*2+1));
        return maxx;
    }
}tree;

int getpos(int L,int R){
    int l=L,r=R,maxx=tree.query(L,R),pos;
    while(l<r){
        int mid=(l+r)/2;
        if(tree.query(L,mid)<maxx)
            l=mid+1;
        else
            pos=r,r=mid;
    }
    if(tree.query(L,l)>=maxx) pos=r;
    return pos;
}

void solve(int L,int R){
    if(L>R) return ;
    int pos=getpos(L,R);

    if(pos-L<=R-pos){
        for(int i=L;i<=pos;i++){
            int rr=min(B[i],R);
            int ll=max(pos,a[pos]-k+i-1);
            if(rr<ll) continue;
            ans+=rr-ll+1;
        }
    }
    else{
        for(int i=R;i>=pos;i--){
            int rr=min(pos,i+1+k-a[pos]);
            int ll=max(L,A[i]);
            if(rr<ll) continue;
            ans+=rr-ll+1;
        }
    }
    solve(L,pos-1);
    solve(pos+1,R);
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T;
    cin>>T;
    while(T--){
        cin>>n>>k;
        ans=0;
        for(int i=1;i<=n;i++) A[i]=1,B[i]=n;
        for(int i=1;i<=n;i++) cin>>a[i];
        tree.maketree(1,n);
        int l=1,r=n;
        for(int i=1;i<=n;i++) cnt[i]=0;
        for(int i=1;i<=n;i++){
            while(cnt[a[i]]>0){
                cnt[a[l]]--;
                l++;
            }
            cnt[a[i]]++;
            A[i]=l;
        }
        for(int i=1;i<=n;i++) cnt[i]=0;
        for(int i=n;i>=1;i--){
            while(cnt[a[i]]>0){
                cnt[a[r]]--;
                r--;
            }
            cnt[a[i]]++;
            B[i]=r;
        }

        solve(1,n);
        cout<<ans<<endl;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值