[APIO2010]特别行动队
我的第一题斜率优化,没看题解就A了,高兴啊O
拿到这题一开始我的想法是根据二次函数的对称性来贪心解决,后来发现这样不仅难以实现而且有严重的BUG。
随后就想到了DP,十分朴素的DP:用f[i]表示前i个队员分组后的最大值,那么可以枚举j,使得j+1..i为一组,计算其中的最大值。令S[i] = a[1]+...+a[i],以及calc(x) = Ax^2+Bx+C f[i] = max{f[j]+calc(S[i]-S[j]) 0 <= j < i
经实验证明,这么做可以拿到30分。
所以考虑一下优化。如果能在决策的时候,也就是选取j的时候,能够在O(1)的时间内完成,那么总体的复杂度就由O(n^2)降至O(n)。
转化
我们考虑一下j1和j2这两个决策,如果决策j2比j1好,那么就有:f[j1]+A*S[j1]*S[j1]-2*A*S[i]*S[j1]-B*S[j1]如果我们令 C[i] = 2*A*S[i], x = S[j] y = f[j]+A*S[j]*S[j]-B*S[j]那么上面那个不等式就可以简化成:-C[i]x1+y1 < -C[i]x2+y2 slope(j1, j2) = (y2-y1)/(x2-x1) > C[i]也就是说,只要slope(j1, j2) > C[i]那么j2就比j1好;反之slope(j1, j2) < C[i]那么j1就比j2好。
slope!斜率优化!用单调队列维护即可。怎么维护呢?取头还是取尾?
我们考虑i2 > i,由于A < 0,因此C[i2] < C[i],故如果j2比j1优:slope(j1, j2) > C[i] > C[i2]也就是说,j1不仅现在不比j2优,以后也没有翻身的机会了,故j1可以很果断的扔掉。这样一来,我们保证队列里面的j满足(其实队列里面存的都是备胎):slope(j1, j2) <= C[i] slope(j2, j3) <= C[i] ... slope(j_x-1, j_x) <= C[i]由上面知道,j1比j2好,j2比j3好…… 故我们做到i时就应该取队列头部的决策。单调性?
对于即将加进来的决策j3,我们假设slope(j1, j2) < slope(j2, j3)如果j2为最好的答案,那么必须满足:slope(j1, j2) > C[i] slope(j2, j3) <= C[i]这显然与假设矛盾,所以我们可以得到:slope(j1, j2) >= slope(j2, j3) >= ... >= slope(j_n, j_n+1)也就是说,这个队列是单调不上升的(其实那个等号取不取问题不是很大,也就是说单调递减也是一样的)实现?
for i:=1 to n do 维护队头满足slope(head, head+1) <= C[i] f[i] = f[tail]+calc(sum[i]-sum[tail]) 维护队尾满足slope(tail-1, tail) >= slope(tail, i) i入队尾2012.04.22 20:00UPDATE:我发现可以不求斜率,直接用前面的不等式来判断,进行入队出队即可。由于不用考虑浮点数除法(是的,下面的程序slope没有用double,是菜过的),因此速度快很多。再Cena上快了0.6s,bzoj上快了400ms(Cena测时间不准),所以其实应该看代码二。代码
PS.其实我很纠结那个%I64d,因为我是Linux的,得用%lld,而Cena在Windows上,得用%I64d……用流的话又太慢了,即使是写了std::ios::sync_with_stdio(false);也一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39#include <cstdio> #include <deque> #include <algorithm> const int MAX(1000000); int n, a[MAX+1], sum[MAX+1]; long long A, B, C, f[MAX+1]; std::deque<int> Q; inline long long calc(const long long x) { return A*x*x+B*x+C; } inline long long slope(const int j1, const int j2) { const long long y1 = f[j1]+A*sum[j1]*sum[j1]-B*sum[j1]; const long long y2 = f[j2]+A*sum[j2]*sum[j2]-B*sum[j2]; return (y2-y1)/(sum[j2]-sum[j1]); } int main() { freopen("commando.in", "r", stdin); freopen("commando.out", "w", stdout); scanf("%dn%I64d %I64d %I64d", &n, &A, &B, &C); for (int i = 1; i <= n; ++i) { scanf("%d", a+i); sum[i] = sum[i-1]+a[i]; } Q.push_back(0); for (int i = 1; i <= n; ++i) { while (Q.size() >= 2 && slope(Q.front(), *(Q.begin()+1)) > 2*A*sum[i]) Q.pop_front(); f[i] = f[Q.front()]+calc(sum[i]-sum[Q.front()]); while (Q.size() >= 2 && slope(*(Q.rbegin()+1), Q.back()) < slope(Q.back(), i)) Q.pop_back(); Q.push_back(i); } printf("%I64d", f[n]); fclose(stdin);fclose(stdout); }
代码二
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41#include <cstdio> #include <deque> #include <algorithm> const int MAX(1000000); int n, a[MAX+1], sum[MAX+1]; long long A, B, C, f[MAX+1]; std::deque<int> Q; inline long long calc(const long long x) { return A*x*x+B*x+C; } inline bool invaild_head(const int i, const int j1, const int j2) { const long long y1 = f[j1]+A*sum[j1]*sum[j1]-B*sum[j1]; const long long y2 = f[j2]+A*sum[j2]*sum[j2]-B*sum[j2]; return (sum[j2]-sum[j1])*2*A*sum[i] < y2-y1; } inline bool invaild_tail(const int i, const int j1, const int j2, const int j3) { const long long y1 = f[j1]+A*sum[j1]*sum[j1]-B*sum[j1]; const long long y2 = f[j2]+A*sum[j2]*sum[j2]-B*sum[j2]; const long long y3 = f[j3]+A*sum[j3]*sum[j3]-B*sum[j3]; return (y2-y1)*(sum[j3]-sum[j2]) < (y3-y2)*(sum[j2]-sum[j1]); } int main() { scanf("%dn%lld %lld %lld", &n, &A, &B, &C); for (int i = 1; i <= n; ++i) { scanf("%d", a+i); sum[i] = sum[i-1]+a[i]; } Q.push_back(0); for (int i = 1; i <= n; ++i) { while (Q.size() >= 2 && invaild_head(i, Q.front(), *(Q.begin()+1))) Q.pop_front(); f[i] = f[Q.front()]+calc(sum[i]-sum[Q.front()]); while (Q.size() >= 2 && invaild_tail(i, *(Q.rbegin()+1), Q.back(), i)) Q.pop_back(); Q.push_back(i); } printf("%lld", f[n]); }