题意: 给n(n<=10^6)个非负数字,放在一个数组num中,再给一个特殊值m。求将这个数组分成任意多个区间,每个区间[a,b]的值定义为( sigma(num[i] | (a<=i<=b)) ) ^ 2 + m.要区间值总和最小,并输出此最小值 (PS:这道题不用考虑暴int问题,当然这是AC以后才发现的)。
解题思路: 定义sum[i]=sigma(num[j] | (1<=j<=i)) (这里假设num数组下标从1开始)
定义f(j,i) = (sum[i]-sum[j])^2+m (区间[j+1,i]的值)
定义dp[i]为从1到i这段的题意要求的那个最小值,根据其定义就有dp[i]=min{ dp[j]+f(j,i) | 0<=j<i } (****)。(思考为什么这样定义?)
另外定义一个仅作标识用的量dp[j,i],表示从j到i这段题意要求的最小区间值和,即dp[1,k]=dp[k]。
裸的枚举当然是不科学的,效率太低。与上一篇博客略有类似,计算dp[i]要用以前的结果,如何对要枚举的状态进行优化呢?
令(k<j<i) ,x>0;
- 若有dp[k]+f(k,i)> dp[j]+f(j,i),那么一定有dp[i+x] > dp[k]+f(k+1,x+i); 也就是说 k一定不是对应着i的一个可能最优解。 在求解dp[i+x]时,k点就不用枚举了。
- 若有dp[k]+f(k,i)<= dp[j]+f(j,i),那么一定有dp[i+x] > dp[j]+f(j,i+x)。
重新思考一下(****)中dp[i]的定义,应用数学归纳法:
dp[1]=f(0,1) 或者dp[1]=f(0,1)+dp[0] (dp[0]=0)
dp[2]=min{dp[1]+f(2,2),dp[0]+f(1,2)} (刚好对应两种区间拆分方式)
假设dp[k]用上式定义也正确,那么
dp[k+1]=min{ dp[1,j]+dp[j+1,k+1] | (j <= k) },如果dp[j+1,k+1]==f(j,k+1),原式自然成立;
那即使不成立呢? 也就是说存在一个j<jj<k, 使得dp[j+1,k+1]==f(j,jj)+dp[jj+1,k+1],所以
dp[1,j]+f(j,jj)+dp[jj+1,k+1]描述的是什么呢?dp[1,j]+f(j,jj)正是dp[jj]的一个可能最优解。dp[k+1]=min{dp[jj]+dp[jj+1,k+1], dp[k+1] },原式成立!
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 #include <algorithm> 5 using namespace std; 6 const int maxn=500100; 7 int num[maxn],pl; 8 int sum[maxn]; 9 int dp[maxn]; 10 int que[maxn]; 11 int f(int j,int i){ 12 return (sum[i]-sum[j])*(sum[i]-sum[j])+pl; 13 } 14 int gety(int j,int k){ 15 return dp[j]+sum[j]*sum[j]-dp[k]-sum[k]*sum[k]; 16 } 17 int getx(int j,int k){ 18 return (sum[j]-sum[k])<<1; 19 } 20 int main() 21 { 22 int n; 23 while(scanf("%d%d",&n,&pl) != EOF){ 24 for(int i=1;i<=n;i++) 25 scanf("%d",&num[i]); 26 sum[0]=dp[0]=0; 27 for(int i=1;i<=n;i++) 28 sum[i]=sum[i-1]+num[i]; 29 int head=0,tail=0; 30 que[tail++]=0; 31 for(int i=1;i<=n;i++){ 32 while(head+1 < tail && gety(que[head+1],que[head])<=getx(que[head+1],que[head])*sum[i]) 33 head++; 34 dp[i]=dp[que[head]]+f(que[head],i); 35 while((head+1 < tail) && gety(i,que[tail-2])*getx(que[tail-1],que[tail-2])<=gety(que[tail-1],que[tail-2])*getx(i,que[tail-2])) 36 tail--; 37 que[tail++]=i; 38 } 39 printf("%d ",dp[n]); 40 } 41 return 0; 42 }