• 牛客2-H施魔法|线段树优化dp,维护区间最小值


    状态转移方程

    题目地址:https://ac.nowcoder.com/acm/contest/3003/H

    先排序。
    假设f[i-1] 表示用掉前i-1个元素的最小代价,那么考虑用第i个元素的最小代价,肯定由前面的第j个元素来推出来的,
    可以由哪些元素推出呢,因为至少拿k个,所以j的范围就是1~i-k+1,这样就可以至少留下 i-(i-k-1)+1 = k个元素组成一组了。

    f[i] = min(f[j-1] + (a[i] - a[j] ) j∈1~i-k+1
    即f[i] = min(f[j-1] - a[j]) + a[i]

    所以只需要维护f[j-1] - a[j]的最小值就行。

    使用线段树优化dp

    使用线段树维护min(f[j-1] - a[j])
    也就是维护前缀区间最小值,dp[i-1] - a[i]

    这样O(n)递推,每次O(logn)查询min(dp[1~j-a[j+1])的最小值,且这里j∈[1,i-k+1]
    总复杂度O(nlogn)

    代码

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    const int maxn = 3e5+10;
    int n,k;
    ll a[maxn];
    ll dp[maxn];
    
    /*
    f[i] = min(f[j-1] + (a[i] - a[j] )   j∈1~i-k+1
    f[i] = min(f[j-1] - a[j]) + a[i]
    
    f[j-1] - a[j] 可以维护这个min
    每次查询1 ~ i-k+1 的 min(f[j-1] - a[i] ) 
    
    dp[i] = min(dp[i],dp[j-1] -a[j])+a[i] ;
    
    用线段树叶子结点维护dp[j-1]-a[j] 也就是 dp[i-1] - a[i]的最小值
    每次O(logn)查询1~j  j∈1~i-k+1的最小值
    更新叶节点dp[i-1] - a[i]即可
     
    */
    
    ll minv[maxn<<2];
    
    void pushup(int o){
        minv[o] = min(minv[o<<1],minv[o<<1|1]);
    }
    //建树 
    void build(int o,int l,int r){
        if(l == r) { //到最后一行了 [1,1] [2,2] ... 
    //        minv[o] = a[l];
            return;
        }
        int mid = (l+r)>>1; 
        build(o<<1,l,mid);
        build(o<<1|1,mid+1,r);
        pushup(o);//向上合并 
    }
    
    
    //a[x] += y : update(1,1,n,x,y); 
    //单点修改  o为当前到达节点的编号 
    void update(int o,int l,int r,int pos,ll v){
        if(l == r){
            minv[o] += v;
            return;
        }   
        int mid = (l+r)>>1;
        if(pos <= mid) update(o<<1,l,mid,pos,v);//前半段区间
        else update(o<<1|1,mid+1,r,pos,v); //后半段区间
        pushup(o); //向上更新  有一些线段树维护了多个值,所以pushup不止一条 
    }
    
    //区间查询 
    ll query(int o,int l,int r,int ql,int qr){
        if(ql<=l && r<=qr) return minv[o];//要查询的区间完全包含了[l,r] 
        ll ans = 1e9+7; //为了求区间最小 初始要设置一个比较大的值 
        int mid = (l+r)>>1;
        if(ql<=mid) ans = min(ans,query(o<<1,l,mid,ql,qr));
        if(qr>mid) ans = min(ans,query(o<<1|1,mid+1,r,ql,qr));
        return ans;
    }
    
    int main(){
    	cin>>n>>k;
    	for(int i=1;i<=n;i++) cin>>a[i];
    	sort(a+1,a+n+1);
    	build(1,1,n);
    	for(int i=1;i<=n;i++) dp[i] = (ll)1e18;
    	dp[k] = a[k] - a[1];
    	//f[i] = min(f[j-1] - a[j]) + a[i] 
    	//使用线段树维护f[i-1]+a[i] 总复杂度O(nlogn) 
    	for(int i=k;i<=n;i++){ //O(n) 递推 
    		ll pre = query(1,1,n,1,i-k+1);  //O(logn) (查询)1~j的最小值 ,这里j∈i-k+1
    		dp[i] = min(dp[i],pre + a[i]);
    		dp[i] = min(dp[i],a[i] - a[1]);
    		update(1,1,n,i,dp[i-1]-a[i]); //O(logn) (更新)dp[i-1]-a[i]的值,从而维护前缀最小值 
    	}
    	cout<<dp[n];
    	return 0;
    }
    
  • 相关阅读:
    唯有坚持方能成功
    Java程序猿,你为什么要关注Scala
    HDU 2871 Memory Control(线段树)
    SQL 合并两个结果集 一个倒序,一个正序
    Charm Bracelet(poj3624)(01背包)
    Dijkstra算法,求单源最短路径(包括路径)
    aix平台安装10.2.0.5的CRS时遇到的bug
    Java语言基础系列之1--用IDEA编写HelloWorld
    UVA 10986 Sending email SPFA
    NumberProgressBar开源项目学习
  • 原文地址:https://www.cnblogs.com/fisherss/p/12276057.html
Copyright © 2020-2023  润新知