P5896 [IOI2016]aliens
*IX. P5896 [IOI2016]aliens
DP 优化方法大杂烩,详解 wqs 二分及其注意事项,斜率优化等其它 DP 优化方法。
更好的阅读体验。
**** 团队赛 T6,没想到是 IOI 原题。当时看出来要用 wqs 二分,但是我没学过(经常发生知道要用某种算法但是不会写的情况),因此最近学习了一下 wqs 二分。找题目练手的时候看到了这题,世 界 线 收 束。做题的过程中发现还要用到斜率优化,于是也学了一下。
这算是一道比较套路的题目,但套路并不意味这不是好题,毕竟我从中学到了 wqs 二分和斜率优化两种 DP 优化方法。说它套路其实也不太合适,因为 2016 年 wqs 二分还没有在 OI 界普及,甚至国外将其称之为 “Aliens’ Trick” 就是根据本题的名字而命名的,所以这个 idea 在当时应该比较新颖。
不妨对网格图的主对角线所经过的格子从左上到右下依次编号为
-
正确性显然。根据该性质,我们可以将所有线段按照左端点 $l_i$ 从小到大为第一关键字,右端点 $r_i$ 从大到小为第二关键字排序。然后依次遍历所有线段并记录右端点最大值 $r$。若当前线段 $i$ 的右端点 $r_i\leq r$,则忽略线段 $i$;否则用 $r_i$ 去更新 $r$,即 $r\gets r_i$。最终所有没有被忽略的线段,一定满足**左端点和右端点都单调递增**。读者自证不难。 -
读者自证不难。 -
读者自证~~不~~很难。感性理解一下即可,严格证明的话应该比较复杂。
有了上述性质,我们可以愉快地进行 DP 了:wqs 二分斜率
这个分情况讨论很讨厌,尝试把它消灭掉:设 但是我不会,所以多讲一点:拆开变成
具体地,记
- 若单调队列大小不小于
2 ,设a,b 分别为队首的第一个和第二个点,若k_{a,b}\leq k=2y_i 则b 一定不劣于a ,弹出队首。重复该操作直到单调队列大小为1 或k_{a,b}>2y_i 。 - 此时队首
a 即为最优决策j ,f_j\to f_i 转移并记录选择的区间个数。 - 若单调队列大小不小于
2 ,设c,d 分别为队尾的倒数第二个和倒数第一个点;若k_{c,d}\geq k_{d,i} 则d 一定不在下凸包上,弹出队尾。重复该操作直到单调队列大小为1 或k_{c,d}<k_{d,i} 。 - 将点
i 加入单调队列。
我们需要求出在代价最小的前提下最少的区间个数。不妨设
时间复杂度
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ld long double
const int N=1e5+5;
const ld eps=1e-10;
ll n,m,k,c,ans;
struct seg{
int l,r;
bool operator < (const seg &v) const{
return l!=v.l?l<v.l:r>v.r;
}
}p[N];
ll K,f[N],g[N],num[N];
ll sq(ll x){return x*x;}
ll Y(int i){return f[i]+sq(p[i+1].l)-g[i]-K;}
ll X(int i){return p[i+1].l;}
ld slope(int i,int j){return (ld)(Y(j)-Y(i))/(X(j)-X(i));}
ll hd,tl,d[N];
bool check(){
d[hd=tl=1]=0;
for(int i=1;i<=n;i++){
while(hd<tl&&slope(d[hd],d[hd+1])+eps<=2*p[i].r)hd++;
int j=d[hd]; num[i]=num[j]+1,f[i]=f[j]+sq(p[i].r-p[j+1].l)-g[j]-K;
if(i<n)while(hd<tl&&slope(d[tl-1],d[tl])-eps>=slope(d[tl],i))tl--;
d[++tl]=i;
} return num[n]<=k;
}
int main(){
cin>>n>>m>>k;
for(int i=1,c,r;i<=n;i++){
cin>>p[i].l>>p[i].r;
if(p[i].l>p[i].r)swap(p[i].l,p[i].r);
} sort(p+1,p+n+1);
for(int i=1,r=-1;i<=n;i++)if(p[i].r>r)r=p[i].r,p[++c]=p[i]; n=c;
for(int i=1;i<n;i++)if(p[i].r>=p[i+1].l)g[i]=sq(p[i].r-p[i+1].l+1);
for(int i=1;i<=n;i++)p[i].r++;
ll l=-1e12,r=0;
while(l<r)K=(l+r>>1)+1,check()?(l=K,ans=f[n]+K*k):r=K-1;
cout<<ans<<endl;
return 0;
}