洛谷题单 算法2-3 分治与倍增

洛谷题单 算法2-3 分治与倍增

P2345 [USACO04OPEN] MooFest G 树状数组

题目链接

思路

让我们求所有两两之间 \(max(v_i,v_j)\times |x_i-x_j|\) 的值之和。

经典思路,考虑每个数对答案的贡献。对于每个数 \(v\),会产生所有比 \(v\) 小的数和它的距离之和倍的

\(v\) 的贡献。考虑如何求解这个距离之和。为了去掉绝对值,我们可以分开讨论,不妨设 \(x_i>x_j\)

那么贡献就可以表示为:\(v_i\times(p\times x_i-sum)\)。其中 \(p\) 为所有小于 \(x_i\) 的数的个数,\(sum\) 为所有小于

\(x_i\) 的数之和。这两部分只需要开两个树状数组即可维护。对于 \(x_i<x_j\) 的情况,同理。

注意:如果存在两个值相同的情况,那么正着反着树状数组维护的时候就会重复计算,因此第二次计算的时候,我们只需要维护 \(v-1\) 即可。

#include<bits/stdc++.h>

using namespace std;

using i64=long long;

const int N = 5e4+10;
void Showball(){
    int n;
    cin>>n;
    vector<int> a(n),b(n),p(n);
    for(int i=0;i<n;i++){
        cin>>a[i]>>b[i];
    }
    iota(p.begin(),p.end(),0);

    sort(p.begin(),p.end(),[&](int x,int y){
        return b[x]<b[y];
    });

    vector<array<i64,2>> tr(N);

    auto add=[&](int op,int x,int v){
        for(;x<N;x+=x&-x) tr[x][op]+=v;
    };

    auto ask=[&](int op,int x){
        i64 ret=0;
        for(;x;x-=x&-x) ret+=tr[x][op];
        return ret;
    };

    i64 ans=0;
    for(int i=0;i<n;i++){
        int v=a[p[i]],id=b[p[i]];
        ans+=1LL*v*(ask(1,v)*id-ask(0,v));
        add(0,v,id);
        add(1,v,1);
    }

    tr.assign(tr.size(),{0,0});
    
    for(int i=n-1;i>=0;i--){
        int v=a[p[i]],id=b[p[i]];
        ans+=1LL*v*(ask(0,v-1)-ask(1,v-1)*id);
        add(0,v,id);
        add(1,v,1);
    }
    cout<<ans<<"\n";
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t=1;
    //cin>>t;

    while(t--){
      Showball();
    }

    return 0;
}

P1966 [NOIP2013 提高组] 火柴排队 树状数组

题目链接

首先对于最小值的情况。根据 排序不等式 , 两列都排序后一定是最优解。

那么我们就可以按照 \(a\) 的顺序给 \(b\) 排序即可(反过来也一样)。因为每次都只能交换

相邻的火柴(其实就是冒泡排序)。那么我们知道最少交换次数= 逆序对 的数量。

我们映射一下 \(a\)\(b\) 数组相对关系,然后求逆序对即可。树状数组求解一下就好。

#include<bits/stdc++.h>

using namespace std;

using i64=long long;

const int mod=1e8-3;
void Showball(){
    int n;
    cin>>n;
    vector<int> a(n+1),b(n+1);

    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++) cin>>b[i];

    vector<i64> tr(n+1);
    auto add=[&](int x,int v){
        for(;x<=n;x+=x&-x) tr[x]+=v;
    };

    auto ask=[&](int x){
        i64 ret=0;
        for(;x;x-=x&-x) ret+=tr[x];
        return ret;
    };

    vector<int> p(n+1),q(n+1);
    iota(p.begin(),p.end(),0);
    iota(q.begin(),q.end(),0);

    sort(p.begin()+1,p.end(),[&](int x,int y){
        return a[x]<a[y];
    });

    sort(q.begin()+1,q.end(),[&](int x,int y){
        return b[x]<b[y];
    });    

    vector<int> t(n+1);
    for(int i=1;i<=n;i++){//t[i]表示a中排第i的数,b中排多少?
        t[p[i]]=q[i];
    }

    i64 ans=0;
    for(int i=n;i;i--){
        ans=(ans+ask(t[i]-1))%mod;
        add(t[i],1);
    }
    cout<<ans<<"\n";
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t=1;
    //cin>>t;

    while(t--){
      Showball();
    }

    return 0;
}

P7167 [eJOI2020 Day1] Fountain 单调栈+倍增

题目链接

先用单调栈维护出每个圆盘下第一个比它直径大的圆盘。

然后倍增维护装水量之和即可。查询类比 \(LCA\)

#include<bits/stdc++.h>

using namespace std;

using i64=long long;

const i64 inf=1e18;
void Showball(){
    int n,q;
    cin>>n>>q;
    vector<int> d(n+2),c(n+2);
    for(int i=1;i<=n;i++) cin>>d[i]>>c[i];
    
    vector<array<i64,25>> f(n+2),g(n+2);
    for(int i=1;i<=n+1;i++){
        for(int j=0;j<=20;j++){
            g[i][j]=inf;
        }
    }
    //单调栈
    stack<i64> stk;
    for(int i=n;i;i--){
        while(stk.size()&&d[stk.top()]<=d[i]) stk.pop();
        if(stk.size()){
            f[i][0]=stk.top();
            g[i][0]=c[stk.top()];
        }else{
            f[i][0]=0;
        }
        stk.push(i);
    } 

    //倍增
    for(int j=1;(1<<j)<=n;j++){
        for(int i=1;i+(1<<j)<=n;i++){
            f[i][j]=f[f[i][j-1]][j-1];
            g[i][j]=g[i][j-1]+g[f[i][j-1]][j-1];
        }
    }
    
    auto query=[&](int r,int v)->i64{
        if(c[r]>=v) return r;
        v-=c[r];
        for(int i=20;i>=0;i--){
            if(f[r][i]&&g[r][i]<v){
                v-=g[r][i];
                r=f[r][i];
            }
        }
        return f[r][0];
    };

    while(q--){
        int r,v;
        cin>>r>>v;
        cout<<query(r,v)<<"\n";
    }

}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t=1;
    //cin>>t;

    while(t--){
      Showball();
    }

    return 0;
}

P3509 [POI2010] ZAB-Frog 单调队列+倍增

题目链接

单调队列维护出每个点下一次跳到的点,然后倍增处理即可。由于跳的次数已经给定了,那么就可以用类似快速幂的写法去写了。

这个单调队列稍微特殊一点,因为距离该点的距离前 \(k\) 小的点一定位于

该点两边,因此我们需要维护区间长度不变,头和尾需要同时更新。

#include<bits/stdc++.h>

using namespace std;

using i64=long long;

void Showball(){
    i64 n,k,m;
    cin>>n>>k>>m;
    vector<i64> a(n+1); 
    for(int i=1;i<=n;i++) cin>>a[i];

    vector<i64> f(n+1);
    f[1]=k+1;
    i64 l=1,r=k+1;
    for(int i=2;i<=n;i++){
        while(r+1<=n&&a[i]-a[l]>a[r+1]-a[i]) l++,r++;
        if(a[i]-a[l]>=a[r]-a[i]) f[i]=l;
        else f[i]=r;
    }

    vector<i64> ans(n+1);
    iota(ans.begin(),ans.end(),0);

    //倍增
    while(m){
        if(m&1){
            for(int i=1;i<=n;i++) ans[i]=f[ans[i]];
        }
        auto nf=f;
        for(int i=1;i<=n;i++) f[i]=nf[nf[i]];
        m>>=1;
    }

    for(int i=1;i<=n;i++) cout<<ans[i]<<" ";

}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t=1;
    //cin>>t;

    while(t--){
      Showball();
    }

    return 0;
}

P4155 [SCOI2015] 国旗计划 双指针+倍增

题目链接

首先,对于环上问题。经典的断环为链,变成线段上的问题。

因为线段不包含,按照左端点排序,右端点也是单调的。

那么我们先考虑如何求每个 \(i\) 的下一段线段。

即找出 \(j\) 满足 \(j=max(x|a_x.l\le a_i.r)\) 。那么这部分就可以用

双指针维护。

接着下来倍增即可。查询时倍增至 \(a_j.r<a_i.l+m\) 即可。

最后答案要加上 \(2\) ,即最后一段线段和第 \(i\) 条线段。

#include<bits/stdc++.h>

using namespace std;

using i64=long long;

struct node{
    int id,l,r;
    bool operator<(const node &u)const{
        return l<u.l;
    }
};
void Showball(){
    int n,m;
    cin>>n>>m;
    vector<node> a(n<<1|1);
    for(int i=1;i<=n;i++){
        int l,r;
        cin>>l>>r;
        if(l>r) r+=m;
        a[i]=node{i,l,r};
    }   

    sort(a.begin()+1,a.begin()+n+1);
    for(int i=1;i<=n;i++){
        a[i+n].l=a[i].l+m;
        a[i+n].r=a[i].r+m;
    }

    //双指针
    vector<array<int,25>> f(n<<1|1); 
    for(int i=1,j=1;i<=2*n;i++){
        while(j<=2*n&&a[j].l<=a[i].r) j++;
        f[i][0]=j-1;
    }

    //倍增
    for(int j=1;(1<<j)<=2*n;j++){
        for(int i=1;i+(1<<j)<=2*n;i++){
            f[i][j]=f[f[i][j-1]][j-1];
        }
    }

    auto query=[&](int id){
        i64 ret=2;
        int lim=a[id].l+m;
        for(int i=20;i>=0;i--){
            if(f[id][i]&&a[f[id][i]].r<lim){
                ret+=(1<<i);
                id=f[id][i];
            }
        }
        return ret;
    };

    vector<int> ans(n+1);
    for(int i=1;i<=n;i++){
        ans[a[i].id]=query(i);
    }

    for(int i=1;i<=n;i++) cout<<ans[i]<<" ";
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t=1;
    //cin>>t;

    while(t--){
      Showball();
    }

    return 0;
}

P6648 [CCC2019] Triangle: The Data Structure 单调队列+倍增

题目链接

本质是一个二维的RMQ问题。

\(f_{i,j,k}\) 表示从 \((i,j)\) 开始大小为 \(2^k\) 的三角形内的最大值。

那么就可以去倍增维护。

空间会超,所以我们需要滚动优化一下。

发现会TLE一部分数据。因为时间复杂度暴了。

观察到中间,我们维护的是固定区间长度的最大值。

那么就可以单调队列进行优化。

#include<bits/stdc++.h>

using namespace std;

using i64=long long;

const int N=3010;

int f[N][N][2];
int a[N][N],q[N];
void Showball(){
    int n,k;
    cin>>n>>k;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=i;j++){
            cin>>a[i][j];
            f[i][j][0]=a[i][j];
        }
    }

    for(int l=1;l<=log2(k);l++){
        for(int i=1;i+(1<<l-1)<=n;i++){
            int t=i+(1<<l-1);
            int len=(1<<l-1)+1;
            int hh=1,tt=0;
            for(int j=1;j<=n;j++){
                f[i][j][l%2]=f[i][j][(l-1)%2];
                while(hh<=tt&&j-q[hh]+1>len) hh++;
                while(hh<=tt&&f[t][j][(l-1)%2]>f[t][q[tt]][(l-1)%2]) tt--;
                q[++tt]=j;
                if(j>=len) f[i][j-len+1][l%2]=max(f[i][j-len+1][l%2],f[t][q[hh]][(l-1)%2]);
            }
        }
    }

    i64 ans=0;
    for(int i=1;i+k-1<=n;i++){
        for(int j=1;j<=i;j++){
            int len=log2(k);
            int maxn=f[i][j][len%2];
            int t=i+k-(1<<len);
            for(int u=j;u<=j+k-(1<<len);u++){
                maxn=max(maxn,f[t][u][len%2]);
            }
            ans+=maxn;
        }
    }

    cout<<ans<<"\n";
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int ttt=1;
    //cin>>t;

    while(ttt--){
      Showball();
    }

    return 0;
}

P7562 [JOISC 2021 Day4] イベント巡り 2 (Event Hopping 2) 思维+倍增

题目链接

\(N\) 条线段,选 \(K\) 条,它们互不相交(但可以在端点相连)。输出字典序最小的选择方案。

因为要输出字典序最小的方案。考虑遍历每个区间,如果该区间能选就一定要选。

因此问题的关键为 如何判断一个区间能不能选

1.判断一个区间与之前已经选择的区间是否有冲突(交叉或者包含)

2.判断选择一个区间之后,剩下的区间可选数量能确保一共够 \(k\)

对于第一个问题:

我们可以用 \(set\) 来维护空闲的区间,定义结构体,并重载运算符。

struct node{
    int l,r;
    bool operator<(const node &u)const{
        return r<u.l;
    }
};

这样我们就可以用 find 函数查到与当前区间 [x,y]相交的区间,然后再判断是否完全包含即可。包含的区间为 [l,r] 。如果包含,那么我们就在 set 中删除 [l,r] ,并且添加 [l,x][y,r] 这两个区间。

对于第二个问题:

我们用 calc(l,r) 表示在 [l,r] 区间中可选区间的最大数量。令 cur 表示当前可选区间数量。

那么每次我们需要去维护 curcur=cur-calc(l,r)+calc(l,x)+calc(y,r)

然后判断剩余可选区间数量 + 已选区间数量是否 \(\geq k\) 即可。

那么如何去求 \(calc(l,r)\) 呢?倍增即可,具体参考代码。

#include<bits/stdc++.h>

using namespace std;

using i64=long long;

const int  N=1e5+10;
struct node{
    int l,r;
    bool operator<(const node &u)const{
        return r<u.l;
    }
};
int f[N<<1][25];
void Showball(){
    int n,k;
    cin>>n>>k;
    vector<int> l(n+1),r(n+1),rk;
    for(int i=1;i<=n;i++){
        cin>>l[i]>>r[i];
        rk.push_back(l[i]);
        rk.push_back(r[i]);
    }
    
    //离散化
    map<int,int> pos;
    sort(rk.begin(),rk.end());
    rk.erase(unique(rk.begin(),rk.end()),rk.end());
    int m=rk.size();
    for(int i=0;i<m;i++) pos[rk[i]]=i+1;
    for(int i=1;i<=n;i++){
        l[i]=pos[l[i]];
        r[i]=pos[r[i]];
    }

    //倍增
    memset(f,0x3f,sizeof f);
    for(int i=1;i<=n;i++){
        f[l[i]][0]=min(f[l[i]][0],r[i]);
    }

    for(int i=m;i>=1;i--){
        f[i][0]=min(f[i][0],f[i+1][0]);
    }
    
    for(int j=1;(1<<j)<=n;j++){
        for(int i=1;i<=m;i++){
            if(f[i][j-1]<=m)
            f[i][j]=f[f[i][j-1]][j-1];
        }
    }

    auto calc=[&](int l,int r){
        int u=l,ret=0;
        for(int i=20;i>=0;i--){
            if(f[u][i]<=r){
                ret+=(1<<i);
                u=f[u][i];
            }
        }
        return ret;
    };

    set<node> st;
    st.insert(node{1,m});
    int cur=calc(1,m);

    vector<int> ans;
    for(int i=1;i<=n;i++){
        int x=l[i],y=r[i];
        auto it=st.find(node{x,y});
        if(it==st.end()) continue;
        auto [L,R]=*it;
        //区间不包含或者选了i之后剩余的区间不够选足k个
        if(L<=x&&y<=R){
            if(cur-calc(L,R)+calc(L,x)+calc(y,R)>=k-ans.size()-1){
                cur=cur-calc(L,R)+calc(L,x)+calc(y,R);
                ans.push_back(i);
                st.erase(it);
                st.insert(node{L,x});
                st.insert(node{y,R});
            }
        }
        if(ans.size()==k){
            for(auto v:ans) cout<<v<<"\n";
            return;
        }
    }
    cout<<"-1";
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t=1;
    //cin>>t;

    while(t--){
      Showball();
    }

    return 0;
}
posted @ 2024-12-02 05:24  Showball  阅读(4)  评论(0编辑  收藏  举报