https://www.lydsy.com/JudgeOnline/problem.php?id=1233
数据结构优化dp的代码总是那么抽象
题意:奶牛们讨厌黑暗。 为了调整牛棚顶的电灯的亮度,Bessie必须建一座干草堆使得她能够爬上去够到灯泡 。一共有N大包的干草(1<=N<=100000)(从1到N编号)依靠传送带连续的传输进牛棚来。第i包干草有一个 宽度W_i(1<=w_i<=10000)。所有的干草包的厚度和高度都为1. Bessie必须利用所有N包干草来建立起干草堆,并且按照他们进牛棚的顺序摆放。她可以相放多少包就放 多少包来建立起tower的地基(当然是紧紧的放在一行中)。接下来他可以放置下一个草包放在之前一级 的上方来建立新的一级。注意:每一级不能比下面的一级宽。她持续的这么放置,直到所有的草包都被安 置完成。她必须按顺序堆放,按照草包进入牛棚的顺序。说得更清楚一些:一旦她将一个草包放在第二级 ,她不能将接下来的草包放在地基上。 Bessie的目标是建立起最高的草包堆。
Input
第1行:一个单一的整数N。 第2~N+1行:一个单一的整数:W_i。
奥妙重重dp题。
第一眼觉得是个反向贪心,从上往下取,每次取最小的可以形成下一层的干草集合,后来发现是个假算法。
反例
6
6 2 3 7 10 11
既然贪心不行,就自然而然想到dp,考虑dp[i]表示到这一层的最大层数,发现不可行,因为这样的dp方法不能固定一个状态。
但是这一题有一个很强的证明:对于一座干草堆而言,基底最小的状态一定为最优状态。
也就是说,每一个状态中,地基最小的状态就是高度最高的状态,我们可以简单的推断一下,每一个高度最高的状态必然可以从下往上贪心的堆干草最终达到基底最小的状态。
这样我们原来的dp就变成了dp[i]表示i -- n种所有干草组成的集合形成的最小的干草堆的基底。
我们反向从N - > 1遍历,对于i,需要找到一个最小的大于i的j满足 sum[j - 1] - sum[i - 1] >= dp[j], 这样就变成了一个O(N * N)的dp,但是这还不够,到这一步需要考虑优化。
我们将转移方程变形 变成sum[i - 1] <= sum[j - 1] - dp[j],注意到我们每次需要的考虑的集合在每一次询问i的时候都多加入1个,以及当存在k < j && sum[k - 1] - dp[k] >= sum[j - 1] - dp[j]的时候,j就是一个多余的状态,sum[i - 1]由于其前缀和的性质,事实上是递减的,对于每一次集合内的查找,我们在查找到最小的j的时候,可以将其余的比sum[i - 1]大的状态全部舍弃。
以上三个性质中,显然可以发现用来维护这个集合的数据结构就是单调队列,最终时间复杂度O(n)
#include <map> #include <set> #include <ctime> #include <cmath> #include <queue> #include <stack> #include <vector> #include <string> #include <cstdio> #include <cstdlib> #include <cstring> #include <sstream> #include <iostream> #include <algorithm> #include <functional> using namespace std; #define For(i, x, y) for(int i=x;i<=y;i++) #define _For(i, x, y) for(int i=x;i>=y;i--) #define Mem(f, x) memset(f,x,sizeof(f)) #define Sca(x) scanf("%d", &x) #define Sca2(x,y) scanf("%d%d",&x,&y) #define Scl(x) scanf("%lld",&x); #define Pri(x) printf("%d ", x) #define Prl(x) printf("%lld ",x); #define CLR(u) for(int i=0;i<=N;i++)u[i].clear(); #define LL long long #define ULL unsigned long long #define mp make_pair #define PII pair<int,int> #define PIL pair<int,long long> #define PLL pair<long long,long long> #define pb push_back #define fi first #define se second typedef vector<int> VI; const double eps = 1e-9; const int maxn = 1e5 + 10; const int INF = 0x3f3f3f3f; const int mod = 1e9 + 7; int N,M,tmp,K; int a[maxn]; int sum[maxn]; int dp[maxn]; int f[maxn]; int Queue[maxn]; int main() { int N; Sca(N); //sum[i - 1] <= sum[j - 1] - dp[j] For(i,1,N){ Sca(a[i]); sum[i] = sum[i - 1] + a[i]; } int head = 1,tail = 1; Queue[1] = N + 1; _For(i,N,1){ while(head < tail && sum[i - 1] <= sum[Queue[head + 1] - 1] - dp[Queue[head + 1]]) head++; dp[i] = sum[Queue[head] - 1] - sum[i - 1]; f[i] = f[Queue[head]] + 1; while(head <= tail && sum[i - 1] - dp[i] >= sum[Queue[tail] - 1] - dp[Queue[tail]]) tail--; Queue[++tail] = i; } printf("%d",f[1]); #ifdef VSCode system("pause"); #endif return 0; }