码迷,mamicode.com
首页 > 其他好文 > 详细

bzoj 3156 防御准备

时间:2016-06-10 21:37:21      阅读:191      评论:0      收藏:0      [点我收藏+]

标签:

Description

技术分享

Input

第一行为一个整数N表示战线的总长度。

第二行N个整数,第i个整数表示在位置i放置守卫塔的花费Ai。

Output

共一个整数,表示最小的战线花费值。

Sample Input

10
2 3 1 5 4 5 6 3 1 2

Sample Output

18

HINT

1<=N<=10^6,1<=Ai<=10^9

 

  这道题显然是一道dp的题。一眼看去,显然可以令f[i]表示第i个位置放防御塔的费用(先说明一下,为了方便计算,我把序列反向后从左到右计算),那么显然有:f[i]=min(f[j]+sigma(1~i-j-1))+a[i]。然而这是个n^2的dp方程,那么怎么做呢?翻了翻网上的题解,发现几乎全部都是写得斜率优化。然而xzy神犇告诉我决策单调性可做。

  怎么做呢?不难发现对于位置i有两个决策j,k(j < k)满足k比j优,那么对于位置大于i的位置也满足这个。证明如下:

 

  若对于j<k且f[j]+sigma(1~i-j-1)>f[k]+sigma(1~i-k-1)

  因为j<k  所以对于x>i有sigma(1~x-j-1)-sigma(1~i-j-1)>sigma(1~x-k-1)-sigma(1~i-k-1)

  所以f[j]+sigma(1~x-j-1)>f[k]+sigma(1~x-k-1)

 

  于是我们就可以做了。我们维护一个单调队列,每次先把队首用不到的区间先弹掉,再用队首元素来更新当前答案,最后用这个解来更新后面的解。当我们发现 对于队尾区间的左端点当前解比队列中储存的解更优时,我们可以直接把这个区间给弹掉(想一想,为什么)。这样弹完以后,若队列中已经没有区间,我们就可以将当前解的区间直接加入队列;否则当前解最优的边界就在队尾的区间中,我们需要在这个区间内二分把这一个边界给找出来,然后更改这个区间的右边界并插入区间。

  这道题是我练习决策单调性优化dp的第一题,总的来说还是有一点难度的。代码如下:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<cmath>
 6 #define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
 7 #define maxn 1000010
 8 #define INF (1LL<<50)
 9 
10 using namespace std;
11 typedef long long llg;
12 
13 int n,a[maxn],l[maxn],r[maxn],x[maxn],lz,rz;
14 llg f[maxn];
15 
16 int getint(){
17     int w=0;bool q=0;
18     char c=getchar();
19     while((c>9||c<0)&&c!=-) c=getchar();
20     if(c==-) q=1,c=getchar();
21     while(c>=0&&c<=9) w=w*10+c-0,c=getchar();
22     return q?-w:w;
23 }
24 
25 inline bool pd(int i,int j,int x){
26     return (f[i]+((llg)(x-i)*(x-i-1)>>1))<=(f[j]+((llg)(x-j)*(x-j-1)>>1));
27 }
28 
29 int main(){
30     File("a");
31     n=getint();
32     for(int i=n;i;i--) a[i]=getint(),f[i]=INF;
33     f[1]=a[1]; f[n+1]=INF;
34     l[rz]=2,r[rz]=n+1,x[rz++]=1; //第一个解需要先处理好
35     for(int i=2;i<=n+1;i++){
36         while(r[lz]<i && lz<rz) lz++; //弹掉没用的区间
37         f[i]=f[x[lz]]+((llg)(i-x[lz])*(i-x[lz]-1)>>1); //更新当前解
38         while(rz>lz && pd(i,x[rz-1],l[rz-1])) rz--; //从队尾弹掉没有当前解优的区间
39         if(lz==rz) l[rz]=i+1,r[rz]=n+1,x[rz++]=i; //直接插入
40         else{
41             int ll=l[rz-1],rr=r[rz-1]+1,mid,xx=x[rz-1]; //在队尾区间内二分
42             while(ll!=rr){
43                 mid=ll+rr>>1;
44                 if(pd(i,xx,mid)) rr=mid;
45                 else ll=mid+1;
46             }
47             r[rz-1]=ll-1; l[rz]=ll,r[rz]=n+1,x[rz++]=i; //修改并插入
48         }
49     }
50     printf("%lld",min(f[n],f[n+1]));
51     return 0;
52 }

bzoj 3156 防御准备

标签:

原文地址:http://www.cnblogs.com/lcf-2000/p/5574163.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!