题目大意:
你有NN座塔一列排开。每座塔各自有高度,有可能相等。
你每次可以选择相邻的两座塔合并在一起,即这两座塔的高度叠加后变成了同一座塔。然后原本分别与这两座塔相邻的塔变得与这座新的塔相邻。
你的目标是使用最少的操作次数在游戏的最后获得一列塔,这些塔的高度从左到右形成一个不下降的数列。
题目思路:
介绍两种算法,非常有启发性的dp
思路一:
设dp[i][k]代表前i个数满足条件,最后满足条件的一段是[k+1,i]
那么则有:
其中 0<=k<i 并且 0<=j<k
显然n^3过不了该题的数据范围
所以考虑根据dp的约束条件加快dp转移:
显然固定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结尾的之前的序列满足要求,并且最后一段的值最小
证明贪心思路是正确的:
一段最小,那么后面可能的合并次数也会跟随减少,贪心是正确的
答案就是回溯倒推一遍即可
很明显转移方程:
复杂度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
***/