[BZOJ4850][JSOI2016]灯塔(分块/决策单调性优化DP)

第一种方法是决策单调性优化DP。

决策单调性是指,设i>j,若在某个位置x(x>i)上,决策i比决策j优,那么在x以后的位置上i都一定比j优。

根号函数是一个典型的具有决策单调性的函数,由于根号函数斜率递减,所以i决策的贡献的增长速度必定比j快。

于是使用基础的决策单调性优化即可。

注意两个问题,一是DP函数要存实数而不能存整数,因为先取整会丢失在后面的判断中需要的信息。二是记录决策作用区间的时候左端点要实时更新,即下面的p[st].l++,否则在二分时会出现错误。

 1 #include<cmath>
 2 #include<cstdio>
 3 #include<algorithm>
 4 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
 5 using namespace std;
 6 
 7 const int N=100010;
 8 double f[N],g[N];
 9 int n,st,ed,h[N];
10 struct P{ int l,r,p; }q[N];
11 
12 int Abs(int x){ return (x>0) ? x : -x; }
13 double cal(int x,int y){ return h[x]-h[y]+sqrt(Abs(y-x)); }
14 
15 int find(P a,int b){
16     int L=a.l,R=a.r;
17     while (L<R){
18         int mid=(L+R)>>1;
19         if (cal(a.p,mid)>=cal(b,mid)) L=mid+1; else R=mid;
20     }
21     return L;
22 }
23 
24 void work(double f[]){
25     st=ed=1; q[1]=(P){1,n,1};
26     rep(i,2,n){
27         q[st].l++; if (q[st].l>q[st].r) st++;
28         f[i]=cal(q[st].p,i);
29         if (st>ed || (cal(q[ed].p,n)<cal(i,n))){
30             while (st<=ed && cal(q[ed].p,q[ed].l)<cal(i,q[ed].l)) ed--;
31             if (st>ed) q[++ed]=(P){i,n,i};
32             else{
33                 int t=find(q[ed],i); q[ed].r=t-1; q[++ed]=(P){t,n,i};
34             }
35         }
36     }
37 }
38 
39 int main(){
40     scanf("%d",&n);
41     rep(i,1,n) scanf("%d",&h[i]);
42     work(f); reverse(h+1,h+n+1);
43     work(g); reverse(g+1,g+n+1);
44     rep(i,1,n) printf("%d\n",max((int)ceil(max(f[i],g[i])),0));
45     return 0;
46 }

第二种方法是分块。

这题中,对于固定的i,sqrt(i-j)只有O(sqrt(n))种取值,而每种取值的区间长度也只有O(sqrt(n))个。

预处理从每个数开始后O(sqrt(n))个数中的最大值,暴力枚举sqrt(i-j)的取值更新答案。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<iostream>
 4 #include<algorithm>
 5 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
 6 typedef long long ll;
 7 using namespace std;
 8 
 9 const int N=100010,K=620;
10 int n,ans,h[N],mx[N][650];
11 
12 int main(){
13     freopen("bzoj4850.in","r",stdin);
14     freopen("bzoj4850.out","w",stdout);
15     scanf("%d",&n);
16     rep(i,1,n) scanf("%d",&h[i]);
17     rep(i,1,n){
18         mx[i][1]=h[i];
19         rep(j,2,min(K,n-i+1)) mx[i][j]=max(mx[i][j-1],h[i+j-1]);
20     }
21     rep(i,1,n){
22         ans=0;
23         for (int pos=i,j=1,nxt; pos!=1; j++)
24             nxt=pos-1,pos=max(pos-j*2+1,1),ans=max(ans,mx[pos][nxt-pos+1]-h[i]+j);
25         for (int pos,j=1,nxt=i; nxt!=n; j++)
26             pos=nxt+1,nxt=min(nxt+j*2-1,n),ans=max(ans,mx[pos][nxt-pos+1]-h[i]+j);
27         printf("%d\n",ans);
28     }
29     return 0;
30 }

 

转载于:https://www.cnblogs.com/HocRiser/p/10122874.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (树形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用树形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵树,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 树形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值