2024杭电多校7——1007创作乐曲

补题链接

也是好久没补题了,欠了好多好多,我的评价是加训

题目如下:
在这里插入图片描述
官方题解:
在这里插入图片描述
官方题解一如既往的简洁, 对于不会做的人要读懂真的挺难 \sout{对于不会做的人要读懂真的挺难} 对于不会做的人要读懂真的挺难

首先询问最少删除多少音符(一般来说不太好处理),我们可以考虑它的对立面,最多保留多少字符这是比较好处理的
我们可以尝试着想出 d p dp dp的方程就是
d p i , 0 = m a x { d p i − 1 , 0 , d p i − 1 , 1 } dp_{i,0} = max\{dp_{i-1,0},dp_{i-1,1}\} dpi,0=max{dpi1,0,dpi1,1}
d p i , 1 = m a x { d p j , 1 : ∣ a i − a j ∣ ≤ k } + 1 dp_{i,1} = max\{dp_{j,1}:|a_i-a_j|\leq k\}+1 dpi,1=max{dpj,1aiajk}+1
d p [ i ] [ 0 ] dp[i][0] dp[i][0]表示前i个字符中,对与第i个字符删除保留的最多字符个数, d p [ i ] [ 1 ] dp[i][1] dp[i][1]同理, 1 1 1代表不删除。

这样并不是很好dp,但是通过题解中发现的性质我们可以在 O ( n q + n l o g n ) O(nq+nlogn) O(nq+nlogn)时间内解决问题.

那么题解的性质是什么意思呢,就是说 d p [ i ] [ 1 ] dp[i][1] dp[i][1]的最优解仅可能被两个位置更新,即在 [ a i − k , a i ] [a_i-k,a_i] [aik,ai]中离 a i a_i ai最近的点 a j 1 a_{j_1} aj1所在的索引 j 1 j_1 j1,和在 [ a i , a i + k ] [a_i,a_i+k] [ai,ai+k]中离 a i a_i ai最近的点 a j 2 a_{j_2} aj2所在的索引 j 2 j_2 j2.

为什么呢?比如说 [ a i − k , a i ] [a_i-k,a_i] [aik,ai] a c a_c ac更新了 d p dp dp值,并且 c < j 1 c<j_1 c<j1,那么它也会尝试去更新 j 1 j_1 j1位置的 d p dp dp值,如果 j 1 j_1 j1位置的 d p dp dp值没有被更新,二者作用等价,如果更新了,那么用 j 1 j_1 j1位置的 d p dp dp值更新可以使得结果更大,所以有那样一个结论.

所以我们可以用线段树维护当前遍历到 i i i时, [ a i − k , a i ] [a_i-k,a_i] [aik,ai] [ a i , a i + k ] [a_i,a_i+k] [ai,ai+k] a i a_i ai最近的点 a j 2 a_{j_2} aj2所在的索引 J J J.每次dp的时候用线段树查询即可,dp的时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
using i128 = __int128;

const int maxn = 1e5+10;
int n,q;
i64 m,k;
int dp[maxn][2]={0};
inline int query(int p,int l,int r,int x,int y,vector<int>&tree){
    if(x<=l&&r<=y) return tree[p];
    int mid = (l+r)>>1;
    int ans = 0;
    if(x<=mid) ans = max(ans,query(p*2,l,mid,x,y,tree));
    if(y>mid) ans = max(ans,query(p*2+1,mid+1,r,x,y,tree));
    return ans;
}

inline void update(int p,int l,int r,int x,int num,vector<int>&tree){
    if(l==r){
        tree[p] = num;
        return;
    }
    int mid = (l+r)>>1;
    if(x<=mid) update(p*2,l,mid,x,num,tree);
    else update(p*2+1,mid+1,r,x,num,tree);
    tree[p] = max(tree[p*2],tree[p*2+1]);
}

inline void solve(){
    cin>>n>>m>>k;
    vector<i64>a(n+1);
    vector<i64>b;
    for(int i = 1;i<=n;++i){
        cin>>a[i];
        b.emplace_back(a[i]);
    }

    sort(b.begin(),b.end());
    int len =b.erase(unique(b.begin(),b.end()),b.end())-b.begin();
    auto find = [&](i64 x){
        return lower_bound(b.begin(),b.end(),x)-b.begin()+1;
    };

    vector<int> tree((len+1)<<2,0);
    vector<int> L(n+1,0),R(n+1,0);
    for(int i = 1;i<=n;++i){
        int l = find(a[i]-k),r = upper_bound(b.begin(),b.end(),a[i]+k)-b.begin(),now  = find(a[i]);
        L[i] = query(1,1,len,l,now,tree);
        R[i] = query(1,1,len,now,r,tree);
        update(1,1,len,now,i,tree);
    }
    cin>>q;
    while(q--){
        int l,r;cin>>l>>r;
        for(int i = l-1;i<=r;++i) dp[i][0]=dp[i][1]=0;
        for(int i = l;i<=r;++i){
            dp[i][0] = max(dp[i-1][0],dp[i-1][1]);
            dp[i][1] = 1;
            if(L[i]>=l&&L[i]<=r) dp[i][1] = max(dp[L[i]][1]+1,dp[i][1]);
            if(R[i]>=l&&R[i]<=r) dp[i][1] = max(dp[R[i]][1]+1,dp[i][1]);
        }
        cout<<r-l+1-max(dp[r][0],dp[r][1])<<"\n";
    }
}

signed main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t;cin>>t;
    while(t--){
        solve();
    }
    return 0;
}



新学到的知识,在q里面开固定长度的vector会超时.
离散化的处理手法,因为我的线段树索引要从1开始用所以,离散化的时候用的是lower_bound(…)+1,对于左界来说这样没有问题,但是对于右界来说就有问题,如果我的右界不在离散化数组里,那么lower_bound(…)本身就相当于已经映射好的正确的离散化右界,+1会出错,这个画个图立得

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值