首页 > 其他分享 >题解 P9415 下楼

题解 P9415 下楼

时间:2023-07-17 23:13:07浏览次数:56  
标签:P9415 int 题解 ll mid query frac id 下楼

不难推理出当初始绳长为 \(len\),需要下降的距离为 \(d\),并且满足 \(d\le len<2d\) 时,最优的环套法可以只损失 \(2d-len\) 长度的绳子,留下 \(2(len-d)\) 的绳子。

当 \(2d\le len\) 时存在一种不损耗绳长的方法可以下到下一根钢管,即把整根绳子系成一个环,到达下面再剪断。

正文:

线段树优化 DP。

我们先将所有钢管按高度从大到小排序,如果高度相同则按权值从小到大排序。

然后将权值离散化,对于相同的 \(v\),给 \(h\) 更大的赋更小的值。

考虑从低往高 DP。

定义 \(f_i\) 表示最初站在 \(i\) 号钢管,至少需要多长的绳子才能下到地面。

转移方程如下:

\[f_{i}=\begin{cases}f_j&2(h_i-h_j)\le f_j\land v_j\ge v_i\\h_i-h_j+\frac{f_j}{2}&2(h_i-h_j)>f_j\land v_j\ge v_i\end{cases} \]

开两棵线段树,第一棵 \(t_1\) 维护 \(\min(f_j)(h_j+\frac{f_j}{2}\ge h_i)\),第二棵 \(t_2\) 维护 \(\min(-h_j+\frac{f_j}{2})(h_j+\frac{f_j}{2}<h_i)\),每次分别在两棵线段树上查询 \([v_i+1,n]\) 内的最小值,记为 \(m_1,m_2\)。那么 \(f_{i}=\min(m_1,m_2+h_i)\)。

容易发现,一根钢管的贡献是有范围的。

对于钢管 \(j\),我们二分出离它最远的 \(i\),满足 \(h_i\le h_j+\frac{f_j}{2}\),那么 \([i,j)\) 这些位置都可以通过第一种转移被 \(f_j\) 贡献到,\([1,i)\) 这些位置可以通过第二种转移被 \(h_i-h_j+\frac{f_j}{2}\) 贡献到。

具体的,我们假设二分到的位置为 \(w\),那么在 \(w-1\) 处标记编号 \(j\),遍历到这里时,将 \(t_1\) 对应位置改为 \(\inf\),\(t_2\) 对应位置改为 \(-h_j+\frac{f_j}{2}\),更新一下线段树即可。

这里之所以可以通过赋值 \(\inf\) 来撤销,是因为离散化后已经保证 \(v_i\) 互不相同了。

注意,由于 \(h\) 在 \(10^{18}\) 级别,如果用 double 会爆精度并喜提 \(0\) 分,所以要用 long double。

如果你还不放心,可以在每次除以 \(2\) 后向上取整,至于为什么对,这里有具体证明。

我们之所以不考虑正着推(从上往下),是因为多一个二分带来的 \(\log\),而且转移时分三段,写起来极其麻烦。

综上,复杂度 \(O(n\log n)\),代码很短。

code:

#include<bits/stdc++.h>
#define ls (id*2)
#define rs (id*2+1)
using namespace std;
typedef long long ll;
const ll N=5e5+5,inf=LLONG_MAX>>2;
ll n,f[N],p;
vector<int>vec[N];
struct node{ll h,v;}a[N];
bool cmph(node x,node y){return x.h!=y.h?x.h>y.h:x.v<y.v;}
bool cmpv(node x,node y){return x.v!=y.v?x.v<y.v:x.h>y.h;}
struct tree{
  ll mi[4*N];
  void change(int id,int l,int r,int x,ll k){
    if(l==r){mi[id]=k;return;}
    int mid=(l+r)>>1;
    if(x<=mid)change(ls,l,mid,x,k);
    else change(rs,mid+1,r,x,k);
    mi[id]=min(mi[ls],mi[rs]);
  }
  ll query(int id,int l,int r,int L,int R){
    if(l==L&&r==R)return mi[id];
    int mid=(l+r)>>1;
    if(R<=mid)return query(ls,l,mid,L,R);
    else if(L>mid)return query(rs,mid+1,r,L,R);
    else return min(query(ls,l,mid,L,mid),query(rs,mid+1,r,mid+1,R));
  }
}t,t2;
inline void get(int id){
  int l=1,r=id,ans;
  while(l<=r){
    int mid=(l+r)>>1;
    if(a[mid].h<=a[id].h+(f[id]>>1))ans=mid,r=mid-1;
    else l=mid+1;
  }
  t.change(1,1,p,a[id].v,f[id]);
  vec[ans-1].push_back(id);
}
int main(){
  ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
  cin>>n;p=n;
  for(int i=1;i<=n;++i)cin>>a[i].h>>a[i].v;
  sort(a+1,a+n+1,cmpv);
  for(int i=1;i<=n;++i)a[i].v=i;
  sort(a+1,a+n+1,cmph);a[n+1].v=++p;
  for(int i=1;i<=4*p;++i)t.mi[i]=t2.mi[i]=inf;
  get(n+1);
  for(int i=n;i>=1;--i){
    for(int j=0;j<vec[i].size();++j){
      int id=vec[i][j];
      t.change(1,1,p,a[id].v,inf);
      t2.change(1,1,p,a[id].v,-a[id].h+((f[id]+1)>>1));
    }
    f[i]=inf;
    f[i]=min(f[i],min(t.query(1,1,p,a[i].v+1,p),t2.query(1,1,p,a[i].v+1,p)+a[i].h));
    get(i);
  }
  cout<<f[1]<<endl;
  return 0;
}

标签:P9415,int,题解,ll,mid,query,frac,id,下楼
From: https://www.cnblogs.com/HQJ2007/p/17561565.html

相关文章

  • 题解 P9437『XYGOI round1』一棵树
    换根DP。本蒟蒻最初没想到换根,把自己写自闭了...定义\(f_u\)为子树\(u\)中的每个结点走到\(u\)的贡献和,\(l_u\)为大于\(a_u\)的最小的\(10\)的幂次方数,\(sum_u\)为\(\sum\limits_{v\inson(u)}{f_v}\)。转移方程为:\(f_u=l_u\cdot\sum\limits_{v\inson(u)}{f_v}+......
  • 决策单调性优化DP 学习笔记 & P4767 [IOI2000] 邮局 题解
    0.题面题目描述高速公路旁边有一些村庄。高速公路表示为整数轴,每个村庄的位置用单个整数坐标标识。没有两个在同样地方的村庄。两个位置之间的距离是其整数坐标差的绝对值。邮局将建在一些,但不一定是所有的村庄中。为了建立邮局,应选择他们建造的位置,使每个村庄与其最近的邮局......
  • 题解 P7215
    点分治。考虑当前的分治重心的城市被完全联通。这可以用队列接解决。每次放入一种城市,就把那些城镇的父亲加入队列,如果存在城镇不在当前分治重心的联通块内,那么说明必定存在另一个分治重心能算到它,直接退出即可。剩下的和模板没有任何区别。复杂度\(O(n\logn)\)。code:#in......
  • 题解 P6329
    点分树模板题。是个神奇的算法。点分树就是将分治重心按照层级连边,每个节点父亲的联通块都包含了当前节点的联通块,且高度为\(\logn\)。可以解决不考虑树的形态的多次询问带修改的问题。对于这道题,我们可以在点分树的每个节点上记录两棵树状数组,分别与当前节点距离为\(k\)的......
  • 题解 P3345
    点分树。本题的核心问题在于不好直接确定补给站的位置。但是仔细思考后我们发现,对于当前节点,如果存在一个儿子的答案比它优,那么补给站一定在那个儿子的子树中。增量为\(w\times(sum_u-2\cdotsum_v)\)。当\(v\)优于\(u\)时,\(2\cdotsum_v>sum_u\)。如果\(v_2\)也满足,则......
  • 题解 P5384
    这题有紫??对于询问节点\(u\),倍增地跳到它的\(k\)级祖先,求其子树内有多少深度为\(dep_u\)的节点。我们考虑把询问离线,再通过dfs序把树拍平,然后扫一遍直接求就行了。复杂度\(O(n\logn)\)。code:#include<bits/stdc++.h>usingnamespacestd;constintN=1e6+5;inlin......
  • 题解 P3806
    点分治模板题。点分治适合处理大规模的树上路径信息问题暴力做法:dfs每个点\(u\),算出其子树内每个点到\(u\)的距离,统计经过\(u\)的所有路径,复杂度\(O(n^2)\)。容易发现,复杂度和子树大小有关。对于当前子树,我们可以求出其重心,计算经过重心的所有路径,删掉重心,递归每个联通......
  • 题解 CF1271D
    贪心+DP。对于一个点,后选显然比先选好,也就是说每个点都对应了唯一一个来源。于是我们可以把每个点所能回溯到的点的收益值从大到小排序,贪心地选前缀。定义\(f_{i,j}\)表示考虑了前\(i\)个点,剩下\(j\)个人,最大收益。转移方程和\(01\)背包的一样。\[f_{i,j}=f_{i-1,j-b......
  • 题解 CF213C
    考虑朴素DP。定义\(f_{i,j,i2,j2}\)表示两个人分别在\((i,j),(i2,j2)\)时获得的最大收益。复杂度\(O(n^4)\),不行。我们换种方法,定义\(f_{st,x,y}\)表示两人同时走了\(st\)步,分别向右走了\(x,y\)步。显然如果向右的步数确定了,向下的也确定了。转移方程如下:\[f_{st,x......
  • 题解 CF1265E
    期望DP。定义\(f_i\)表示第\(i\)个镜子照成功的期望天数,\(p_i\)为第\(i\)天成功的概率,\(q_i\)为第\(i\)天失败的概率。根据题意容易列出方程:\[f_i=(f_{i-1}+1)\cdotp_i+(f_{i-1}+1+f_i)\cdotq_i\]移项得:\[(1-q_i)\cdotf_i=(f_{i-1}+1)\cdot(p_i+q_i)\]同除以......