【信息学奥赛一本通——高手训练】塔 | 贪心、线性dp、思维

题目大意:

你有NN座塔一列排开。每座塔各自有高度,有可能相等。

你每次可以选择相邻的两座塔合并在一起,即这两座塔的高度叠加后变成了同一座塔。然后原本分别与这两座塔相邻的塔变得与这座新的塔相邻。

你的目标是使用最少的操作次数在游戏的最后获得一列塔,这些塔的高度从左到右形成一个不下降的数列。

题目思路:

介绍两种算法,非常有启发性的dp

思路一:

设dp[i][k]代表前i个数满足条件,最后满足条件的一段是[k+1,i]

那么则有:

dp[i][k] = Min_{sum_i-sum_k >= sum_k - sum_j} {dp[k][j] + i-k-1}

其中 0<=k<i  并且 0<=j<k 

显然n^3过不了该题的数据范围

所以考虑根据dp的约束条件加快dp转移:

sum_i - sum_k >= sum_k - sum_j => 2*sum_k <= sum_i + sum_j

显然固定i,k固定之后,由于sumj是递增的,所以可以快速得出第一个满足条件的j,然后只需要求一个后缀最小值的dp[k][j]即可了

当然既然可以二分那么必然可以尺取,这里直接采用了尺取,然后维护一个后缀最小值进行转移即可

Code:

/*** keep hungry and calm CoolGuang!***/
#pragma GCC optimize("Ofast","unroll-loops","omit-frame-pointer","inline")
#pragma GCC optimize(3)
#include <bits/stdc++.h>
#include<stdio.h>
#include<queue>
#include<algorithm>
#include<string.h>
#include<iostream>
#define debug(x) cout<<#x<<":"<<x<<endl;
#define d(x) printf("%lld\n",x);
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
const ll INF= 1e17;
const ll maxn = 3e6+700;
const int mod= 1e9+7;
template<typename T>inline void read(T &a){char c=getchar();T x=0,f=1;while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}a=f*x;}
ll n,m,p;
ll dp[3005][3005],cur[3005][3005];;
ll sum[maxn];
ll num[maxn];
int main(){
    read(n);
    for(int i=1;i<=n;i++){
        read(num[i]);
        sum[i] = sum[i-1] + num[i];
    }
    for(int i=0;i<=n;i++)
        for(int k=0;k<=n;k++)
            dp[i][k] = cur[i][k] = INF;
    dp[1][0] = 0;
    for(int i=1;i<=n;i++) dp[i][0] = i-1;
    for(int i=1;i<=n;i++){
        ///sum[i]-sum[k] >= sum[k]-sum[j]
        int last = 0;
        for(int k=0;k<i;k++){
            while(last < k && sum[i]-sum[k] < sum[k]-sum[last]) last++;
            if(last<k) dp[i][k] = min(dp[i][k],cur[k][last]+i-k-1);
        }
        for(int k=i-1;k>=0;k--)
            cur[i][k] = min(cur[i][k+1],dp[i][k]);
    }
    ll ans  = INF;
    for(int k=0;k<n;k++)
        ans = min(dp[n][k],ans);
    printf("%lld\n",ans);
    return 0;
}

思路二:

考虑贪心,dpi代表以i结尾的之前的序列满足要求,并且最后一段的值最小

证明贪心思路是正确的:

一段最小,那么后面可能的合并次数也会跟随减少,贪心是正确的

答案就是回溯倒推一遍即可

很明显转移方程:dp_i = min_{sum_i-sum_k >= dp_k}\{sum_i-sum_k\}

复杂度O(n^2) 

但是留出了优化的空间

考虑约束条件 : sum[i] - sum[k] >= dp[k] =>  sum[i] >= sum[k]+dp[k]

所以可以以sum[k]+dp[k] 为下标,-sum[k]为权值建树,在nlogn之内维护出最优解

整体复杂度:O(nlogn)

Code:

/*** keep hungry and calm CoolGuang!***/
#pragma GCC optimize("Ofast","unroll-loops","omit-frame-pointer","inline")
#pragma GCC optimize(3)
#include <bits/stdc++.h>
#include<stdio.h>
#include<queue>
#include<algorithm>
#include<string.h>
#include<iostream>
#define debug(x) cout<<#x<<":"<<x<<endl;
#define d(x) printf("%lld\n",x);
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
const ll INF= 1e17;
const ll maxn = 3e6+700;
const int mod= 1e9+7;
template<typename T>inline void read(T &a){char c=getchar();T x=0,f=1;while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}a=f*x;}
ll n,m,p;
ll dp[maxn];///以i结尾 满足要求 最后结尾的和的最小值
ll num[maxn];
ll sum[maxn];
int main(){
    read(n);
    for(int i=1;i<=n;i++){
        read(num[i]);
        sum[i] = sum[i-1] + num[i];
        dp[i] = INF;
    }
    dp[0] = 0;
    for(int k=1;k<=n;k++){
        for(int j=0;j<n;j++){
            if(sum[k]-sum[j]>=dp[j])
                dp[k] = min(dp[k],sum[k]-sum[j]);
            d
        }
    }
    int ans = 0,last = n,lastans = dp[n];
    for(int i=n;i>=0;i--){
        if(sum[last]-sum[i] == lastans){
            ans++;
            lastans = dp[i];
            last = i;
        }
    }
    printf("%d\n",n-ans);
    return 0;
}
/***
5 5 3
1 2 3 4 5
3 3
3 2
1 5
***/

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只酷酷光儿( CoolGuang)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值