[BZOJ]1010: [HNOI2008]玩具装箱toy——斜率优化动态规划——Update:2012年5月13日
[题目大意]
- 给定N个物品,可以连续的划分为若干个组,每个组的代价是(物品数-1+每个物品单独的代价-L)^2,求最小代价
[分析题解]
- 朴素F[I]:=Min(F[J]+(J-I-1+Sum[I]-Sum[J]-L)^2)
- 当然会TLE
- 尝试着整理变形然后查看单调性,因为看到有人说可以把决策表打出来看看单调性,于是尝试了一下这个方法。随便学习了一下批处理的皮毛,以前写的对拍程序只能够手工一次一次对拍,现在终于能够写出一个更加自动化的程序来了
- 批处理1 :loop
2 B1010_MakeData
3 B1010
4 B1010_Check
5 fc B1010_Check.out B1010.txt /a | find "no differences" > nul && goto :loop
6 PAUSE
7
8 ——————————割————————————
9 :loop//标号
10 B1010_MakeData//数据生成器
11 B1010//朴素程序
12 B1010_Check//对拍程序
13 fc B1010_Check.out B1010.txt /a | find "no differences" > nul && goto :loop
14 PAUSE
15 "|"的作用是将前一个命令的返回值作为后一个程序的输入,第5句的作用是,如果fc程序返回的信息中没有"no differences"这个字串就跳出,否则返回标号loop.
16 "&&"的作用是,如果前面的命令为真则执行后面 以后就不用盯着了,将PAUSE命令去掉之后就可以再任务栏中的图标消失后直接去查看.in找毛病了
- 然后我就生成了很多数据,用朴素程序将每一个决策点打印了出来,又用另外的一个程序来检测是不是单调,发现真的非常单调
- 这样尝试着能不能斜率优化
- 别人非常条理的题解纠结了半天的题,不过总算完全弄明白斜率优化这思想了。
DP方程不难想:
DP[I]=MIN(DP[J]+(SUM[I]-SUM[J]+I-J-1-L)^2) ,J<I
其中DP[I]表示处理到第I个玩具时最少的费用。
直接来显然TLE,一维方程一般用斜率优化。
先化简下方程:
令F[I]=SUM[I]+I,C=1+L,则化为
DP[I]=MIN(DP[J]+(F[I]-F[J]-C)^2)
以下是用这方法的步骤:
1.证明较优决策点对后续状态影响的持续性
假设在状态I处,有两个决策点J,K(J<K),且决策K比决策J好,即
DP[K]+(F[I]-F[K]-C)^2<=DP[J]+(F[I]-F[J]-C)^2
则,对于状态I之后的某状态T, F[T]=F[I]+V
要证
DP[K]+(F[T]-F[K]-C)^2<=DP[J]+(F[T]-F[J]-C)^2
只需证
DP[K]+(F[I]+V-F[K]-C)^2<=DP[J]+(F[I]+V-F[J]-C)^2
只需证
DP[K]+(F[I]-F[K]-C)^2+2*(F[I]-F[K]-C)*V+V^2<=DP[J]+(F[I]-F[J]-C)^2+2*(F[I]-F[J]-C)*V+V^2
联系假设只需要证:
2*(F[I]-F[K]-C)*V<=2*(F[I]-F[J]-C)*V
即证:
F[K]>=F[J]
F[I]显然为增函数,且J<K,所以上式得证.
2.求斜率方程:一般化为左边是J,K,右边是I的形式
由DP[K]+(F[I]-F[K]-C)^2<=DP[J]+(F[I]-F[J]-C)^2
展开得:
DP[K]+F[I]^2-2*F[I]*(F[K]+C)+(F[K]+C)^2<=DP[J]+F[I]^2-2*F[I]*(F[J]+C)+(F[J]+C)^2
即:
DP[K]+(F[K]+C)^2-DP[J]-(F[J]+C)^2<=2*F[I]*(F[K]-F[J])
即
(DP[K]+(F[K]+C)^2-DP[J]-(F[J]+C)^2)/(2*(F[K]-F[J]))<=F[I]
令
G(K,J)=DP[K]+(F[K]+C)^2-DP[J]-(F[J]+C)^2
S(K,J)=2*(F[K]-F[J])
X(K,J)=G(K,J)/S(K,J)
所以斜率方程为X(K,J)<=F[I]
3.规定队列的维护规则
队首维护:
假设A,B(A<B)是队首元素,若X(B,A)<=F[I],则B比A好,删除A,否则不需维护.
队尾维护:
假设A,B,C(A<B<C)是队尾元素
a.若X(B,A)<=F[I],且X(C,B)<=F[I],则C比B好,B比A好
b.若X(B,A)<=F[I],且X(C,B)>F[I],则B比C好,B比A好,B为极大值
c.若X(B,A)>F[I],A比B好
a,c情况直接删掉B,b情况保留.b情况可改为X(B,A)<X(C,B)
AC CODE:
//[HNOI2008]玩具装箱toy 1180K 136MS
//地址:http://61.187.179.132:8080/JudgeOnline/showproblem?problem_id=1010
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
int MIN(int a,int b) { if( a<b ) return a; else return b; }
int MAX(int a,int b) { if( a>b ) return a; else return b; }
#define CLR(NAME,VALUE) memset(NAME,VALUE,sizeof(NAME))
using namespace std;
typedef __int64 LL;
const int N=50000+10;
LL f[N];
LL dp[N];
int q[N];
LL G(int k,int j,int& c) { //在化简公式不小心把C的符号弄错了,调试了很久才发现...
return dp[k]+(f[k]+c)*(f[k]+c)-dp[j]-(f[j]+c)*(f[j]+c);
}
LL S(int k,int j) {
return 2*(f[k]-f[j]);
}
int main() {
int n,l,c,i,j,x,y,z,tmp;
while( scanf("%d%d",&n,&l)!=EOF ) {
f[0]=0;
for(i=1;i<=n;++i) {
scanf("%d",&tmp);
f[i]=f[i-1]+tmp;
}
for(i=1;i<=n;++i) {
f[i]+=i;
}
c=1+l;
dp[0]=0;
int head=0,tail=0;
q[tail++]=0;
for(i=1;i<=n;++i) {
while( head<tail-1 && G(q[head+1],q[head],c)<=f[i]*S(q[head+1],q[head]) ) {
++head;
}
x=q[head];
dp[i]=dp[x]+(f[i]-f[x]-c)*(f[i]-f[x]-c);
q[tail++]=i;
for(j=tail-2;j>head;--j) {
z=q[j+1];
y=q[j];
x=q[j-1];
if( !(G(y,x,c)*S(z,y)<G(z,y,c)*S(y,x)) ) {
q[j]=q[--tail];
}
else {
break;
}
}
}
printf("%I64d\n",dp[n]);
}
return 0;
} - 斜率优化基本都是这么处理的
- 先备个案吧,回头再多复习复习
- 然后这个地方有各种各样的动归,也备个案,回头一并A掉
[另外]
Usaco练习组中有一道题目叫做fc,是一道关于凸包的问题,前一段时间我重新写了一遍,使用的文件输入,屏幕输出。今天在使用批处理的时候,发现fc命令不断的返回很多的数值,像是坐标一样,我找了很多的地方,都没有找到错误。最后才恍然大悟,是fc这个程序的问题。批处理在调用的时候,并没有调用Dos中的fc,而是在目录下调用了fc这个凸包程序。又因为使用了文件输入,屏幕输出,所以直接输出了凸包的坐标而没有提示要求输入。
有时候一个程序中某个子程序有问题的时候,不要只盯着这一个子程序看,问题也有可能出在其他地方。——调试程序须知
[Update at 2012-5-13]
写了一下这个题目,但是对队尾的维护做了修改,没有使用三点测试,而是直接测试队尾元素与这次计算出的I元素对于I+1的优劣性。毫无疑问的WA了。于是考虑下为什么,说实话这是我第一次接触这种G/S的表示形式,以前我都是设一个(X,Y),将一个决策点表示为一个坐标系里的点的,然后根据单调性维护凸包(《用单调性优化动态规划》)。但是我考虑了很久很久之后,明白了其中的原因,这种G/S本质上是于(X,Y)模式是一样的,其对队尾三个点的操作等价于维护凸包。想明白了这个我就彻底明白了这种G/S的表示形式,说实话,这样处理起来的确是更美观了一些。
——————————————————————————————————————
你说,我们的存在,永不消逝。对吧?
如果,我们都在努力创造了存在。我们,会幸福的。对吧?