[DP记录]AT2347 [ARC070C] NarrowRectangles

command_block

2021-05-12 08:21:54

Personal

**题意** : 有 $n$ 个宽为 $1$ 的木板从左到右排成一排,第 $i$ 个木板覆盖了纵坐标 $[l_i,r_i]$。 现在可以上下移动这些木板,使得所有木板连接起来(有一点接触就算连接)。问最小的移动距离和。 $n\leq 10^5$ ,时限$\texttt{2s}$。 ------------ 先考虑 $\rm DP$。 设 $f_{i,x}$ 表示处理了前 $i$ 个木板,最后一个木板的左端点放在 $x$ ,所需的最小移动距离和。 显然,需要考虑的 $x$ 均为整数。 不难写出转移方程 : $$f_{i+1,x}=|x-l_{i+1}|+\min _{k\in[x-len_i,x+len_{i+1}]}f_{i,k}$$ $|x-l_i|$ 是下凸函数,凸函数的区间 $\rm min$ 也是凸函数,于是不难归纳证明 $f_{i,x}$ 是关于 $x$ 的凸函数。 将 $f_{i,x}$ 改记为 $f_i(x)$。 记 $f_i(x)$ 的最小值位置区间为 $[L_i,R_i]$ ,则可以写出转移方程 : $$f_{i+1}(x)=|x-l_{i+1}|+\begin{cases}f_{i-1}(x+len_i)&(x<L_i-len_i)\\f_{i-1}(L_i)&(x\in[L_i-len_{i},R_i+len_{i+1}])\\f_{i-1}(x-len_{i+1})&(x>R_i+len_{i+1})\end{cases}$$ 这个方程相当于将 $f_i(x)$ 的左侧向左移动 $len_i$ ,右侧向右移动 $len_{i+1}$ ,然后加上一个绝对值函数。 对于绝对值函数的处理,分三类讨论 : 用两个堆分别维护左右侧的凸壳,每个元素代表一个斜率变化 $1$ 的拐点。 记加绝对值之前的函数,最小值区间为 $[L,R]$ ,记 $c=l_{i+1}$。 - **情况A** : $c\in [L,R]$ 最小值位置变为 $l_{i+1}$ 。左侧斜率减一,右侧斜率加一,给左右两个堆各加入一个 $c$。 - **情况B** : $c<L$ 查看 $L$ 左侧的第一段斜率为 $-1$ 的区间,假设为 $[T,L]$。 则新的最小值区间为 $[\max(c,T),L]$。在左堆加入两个 $c$ ,因为斜率变化了两次。 - **情况C** : $c>R$ 情况类似,查看 $R$ 右侧的第一段斜率为 $1$ 的区间,假设为 $[R,T]$。 则新的最小值区间为 $[R,\min(c,T)]$。在右堆加入两个 $c$。 需要给堆维护一个加法标记。 还要考虑如何求解答案。记 $ans_i$ 为 $f_{i}(x)$ 的最小值 ,则 : - **情况A** : 最小值不变。 - **情况B** : 最小值加上 $L-c$ - **情况C** : 最小值加上 $c-R$ 复杂度 $O(n\log n)$。 ```cpp #include<algorithm> #include<cstdio> #include<queue> #define ll long long #define MaxN 100500 using namespace std; int n;ll len[MaxN]; priority_queue<ll> ql; priority_queue<ll,vector<ll>,greater<ll> > qr; int main() { ll x,tl=0,tr=0,ans=0; scanf("%d%lld%lld",&n,&x,&len[1]); len[1]-=x;ql.push(x);qr.push(x); for (int i=2;i<=n;i++){ scanf("%d%d",&x,&len[i]); len[i]-=x; tl-=len[i];tr+=len[i-1]; ll L=ql.top()+tl,R=qr.top()+tr; if (L<=x&&x<=R){ql.push(x-tl);qr.push(x-tr);} else if (x<L){ ans+=L-x; ql.pop();qr.push(L-tr); ql.push(x-tl);ql.push(x-tl); }else { ans+=x-R; qr.pop();ql.push(R-tl); qr.push(x-tr);qr.push(x-tr); } }printf("%lld",ans); return 0; } ```