POJ_3017
这个题目动规的方程是很好写出来的f[i]=min{f[j]+max[j+1,i]},其中j要满足sum[j+1,i]<=M。
如果裸着做的话显然是O(N^2)的复杂度,而动规的维数显然是一维,不能再减了,于是我们要试图减少决策的数量,换句话说对于每个i,我们要在O(1)或者O(logN)或者同级别的时间复杂度内找到最优的决策(decision)j,直接令f[i]=f[j]+max[j+1,i]。
先不说别的,这个max要怎么求呢?RMQ问题固然可以预处理出来,但实际上没必要那么麻烦,我们可以维护一个单调队列,对于当前的a[i],先将队尾小于或等于a[i]的元素删掉,再把a[i]插入队尾。比如拿sample中的标号4-8一段8 1 8 2 1来看,那么队列里面存的标号就是6 7 8,对应的a[]的值就是8 2 1。
那么这样我们就会发现一个现象,如果标号小于或等于6,也就是4、5、6,显然max[x,8]=8,如果标号大于6但是小于或等于7,也就是7,显然max[x,8]=2,如果标号大于7但是小于或等于8,也就是8,显然max[x,8]=1。也就是说,这些单调队列中的元素把这个区间划分成了若干段(4-6一段,7-7一段,以及8-8一段),在每段中都有一个共同的max。
但是这又有什么用呢?接下来就将揭晓。
我们不妨假设现在要计算f[8],同时我们先不理会M的限制,就假定我们现在至多把4-8中某一段[x,8]作为最后一个区间,那么对于x取4-6时这一段你会选择哪个决策点作为最优决策呢?显然是3(这里的决策点是指的j,所以对于x取4-6这一段,决策点是3-5)。这是因为f[j]随着j的增大单调非降的,换句话说就是max[4,8]、max[5,8]、max[6,8]的值都是一样的,而由于f[3]<=f[4]<=f[5],那么我们必然选f[3]+max[4,8]去更新f[8]。对于7-7这段和8-8这段也是一样的道理。
通过上一段的分析,我们发现了一个比较好的现象,我们维护的a[]的单调队列中有多少个元素就会有多少个备选决策(因此我们可以看成这个单调队列中存放的不只有a[],同时还有与之对应的备选决策),我们只要用一个数据结构(比如堆、二叉查找树等)比较快地在这些决策中找到最优的就可以解决这个问题了(实际上就是对所有的f[j]+max[j+1][i]求一个最小值)。
但这样有个问题需要搞清楚,对于前面更新i-1的一个备选决策f[j]+max[j+1,i-1],到了更新f[i]的时候它的值会变吗?如果会改变的话我们就徒劳了,因为我们每循环到一个i就要更新一遍队列中对应的所有决策,这样最坏情况必然是O(N^2)的。
然而可喜的是,假如f[j]+max[j+1,i-1]是更新f[i-1]的备选决策之一,且循环到i时它仍在队列中,那么它的值就不会改变。为什么呢?我们不妨假设它的值会改变,那就说明a[i]>=max[j+1,i-1](之所以有等于,是考虑到单调队列中相同的值只能有一个,而且是标号比较大的那个),那么根据我们维护单调队列的规则,由于max[j+1,i-1]<=a[i],在插入a[i]的过程中它就应该被删除了,这样就矛盾了。既然a[i]>=max[j+1,i-1]是不成立的,那么就会有a[i]<max[j+1,i-1],进而有max[j+1,i-1]=max[j+1,i],这样就有f[j]+max[j+1,i-1]=f[j]+max[j+1,i],它又顺理成章地成了更新f[i]时的备选决策之一。
当然,这里的分析都没有考虑M的限制,但不难得到考虑M的时候只不过每次要先从队首删掉一些元素而已。
分析到这里,我们要做的工作就比较明确了。首先要维护a[]的一个单调队列,里面存的是a[]的标号,另外与这个队列对应的会有同等数量的备选决策,而为了方便的得到备选决策中的最小值,我们可以用平衡树或者堆存下来,每次只要找到备选决策中最优的一个去更新f[i]即可。
此外,还有一个细节需要注意一下,对于单调队列中的每个a[],我们如何确定其对应的决策点呢?假如这个a[]是队列中间的某个值y,也就是队列是这样的形式…,x,y,…,那么a[y]对应的决策点显然就是j=x,因为如果j<x,那么max[j+1,i]就会变成a[x]了,就不再是a[y]了。也就是说队列中的点的决策点可以参考队列中的前一个点,那么队首呢?我们就要单独维护一个变量decision了。
每次先要根据M的限制修改decision,之后根据decision删除一些队首的点,然后将a[i]从队尾开始插入到合适的位置,最后选取最优的决策更新f[i]。
#include<stdio.h>
#include<string.h>
#define MAXD 100010
int T, N, node, left[MAXD], right[MAXD], size[MAXD], a[MAXD];
long long int M, key[MAXD], f[MAXD], A[MAXD];
int pool[MAXD], top, q[MAXD], d[MAXD];
void newnode(int &T, int v)
{
if(top)
T = pool[-- top];
else
T = ++ node;
key[T] = v;
size[T] = 1;
left[T] = right[T] = 0;
}
void leftrotate(int &T)
{
int k = right[T];
right[T] = left[k];
left[k] = T;
size[k] = size[T];
size[T] = size[left[T]] + size[right[T]] + 1;
T = k;
}
void rightrotate(int &T)
{
int k = left[T];
left[T] = right[k];
right[k] = T;
size[k] = size[T];
size[T] = size[left[T]] + size[right[T]] + 1;
T = k;
}
void maintain(int &T, int flag)
{
if(flag == 0)
{
if(size[left[left[T]]] > size[right[T]])
rightrotate(T);
else if(size[right[left[T]]] > size[right[T]])
leftrotate(left[T]), rightrotate(T);
else
return ;
}
else
{
if(size[right[right[T]]] > size[left[T]])
leftrotate(T);
else if(size[left[right[T]]] > size[left[T]])
rightrotate(right[T]), leftrotate(T);
else
return ;
}
maintain(left[T], 0);
maintain(right[T], 1);
maintain(T, 0);
maintain(T, 1);
}
void Insert(int &T, int v)
{
if(T == 0)
{
newnode(T, v);
return ;
}
++ size[T];
if(v < key[T])
Insert(left[T], v);
else
Insert(right[T], v);
maintain(T, v >= key[T]);
}
int Delete(int &T, int v)
{
-- size[T];
if(key[T] == v || (v < key[T] && left[T] == 0) || (v >= key[T] && right[T] == 0))
{
int k = key[T];
if(left[T] == 0 || right[T] == 0)
pool[top ++] = T, T = left[T] + right[T];
else
key[T] = Delete(left[T], v + 1);
return k;
}
if(v < key[T])
return Delete(left[T], v);
else
return Delete(right[T], v);
}
int searchmin(int &T)
{
if(left[T] == 0)
return key[T];
else
return searchmin(left[T]);
}
int init()
{
int i, ok = 1;
T = node = top = left[0] = right[0] = size[0] = 0;
A[0] = 0;
for(i = 1; i <= N; i ++)
{
scanf("%d", &a[i]);
A[i] = A[i - 1] + a[i];
if(a[i] > M)
ok = 0;
}
return ok;
}
void solve()
{
int i, j, k, front, rear, decision;
decision = front = rear = 0;
for(i = 1; i <= N; i ++)
{
while(A[i] - A[decision] > M)
++ decision;
while(front < rear && q[front] <= decision)
{
Delete(T, f[d[front]] + a[q[front]]);
++ front;
}
while(front < rear && a[i] >= a[q[rear - 1]])
{
Delete(T, f[d[rear - 1]] + a[q[rear - 1]]);
-- rear;
}
q[rear] = i;
if(front < rear)
d[rear] = q[rear - 1];
else
d[rear] = decision;
Insert(T, f[d[rear]] + a[i]);
++ rear;
if(d[front] < decision)
{
Delete(T, f[d[front]] + a[q[front]]);
d[front] = decision;
Insert(T, f[decision] + a[q[front]]);
}
f[i] = searchmin(T);
}
printf("%lld\n", f[N]);
}
int main()
{
while(scanf("%d%lld", &N, &M) == 2)
{
if(!init())
printf("-1\n");
else
solve();
}
return 0;
}