1 条题解
-
0
DP 优化方法大杂烩,详解 wqs 二分及其注意事项,斜率优化等其它 DP 优化方法。
**** 团队赛 T6,没想到是 IOI 原题。当时看出来要用 wqs 二分,但是我没学过(经常发生知道要用某种算法但是不会写的情况),因此最近学习了一下 wqs 二分。找题目练手的时候看到了这题,世 界 线 收 束。做题的过程中发现还要用到斜率优化,于是也学了一下。
这算是一道比较套路的题目,但套路并不意味这不是好题,毕竟我从中学到了 wqs 二分和斜率优化两种 DP 优化方法。说它套路其实也不太合适,因为 2016 年 wqs 二分还没有在 OI 界普及,甚至国外将其称之为 “Aliens’ Trick” 就是根据本题的名字而命名的,所以这个 idea 在当时应该比较新颖。
不妨对网格图的主对角线所经过的格子从左上到右下依次编号为 。显然,编号为 的格子在原网格图中的坐标为 。因为正方形的主对角线完全包含于网格图的主对角线,所以为了拍到一个点 必然要拍到编号为 的格子,其中 。因此,我们可以将题目转化为线段覆盖问题:现在有 条线段 ,我们要用最多 个区间 去覆盖它们,使得每条线段至少被一个区间完全包含。即 。求以每个区间为对角线形成的所有正方形的面积并的最小值。接下来我们可以挖掘一些性质:
-
如果两条线段 满足 ,那么线段 对最终的答案没有影响。
正确性显然。根据该性质,我们可以将所有线段按照左端点 从小到大为第一关键字,右端点 从大到小为第二关键字排序。然后依次遍历所有线段并记录右端点最大值 。若当前线段 的右端点 ,则忽略线段 ;否则用 去更新 ,即 。最终所有没有被忽略的线段,一定满足左端点和右端点都单调递增。读者自证不难。
-
最终所选取的所有区间一定满足 。
读者自证不难。
-
设 为恰好选取 个区间时答案的最小值,那么 是一个下凸函数。
读者自证
不很难。感性理解一下即可,严格证明的话应该比较复杂。
有了上述性质,我们可以愉快地进行 DP 了:wqs 二分斜率 ,然后设 表示覆盖第 条线段时的最小代价(线段按照左端点从小到大排序。根据性质 的结论,右端点也行,因为不影响线段之间的顺序)。显然有转移方程:
$$f_i=\min_{j=0}^{i-1}-K+\begin{cases}f_j+(y_i-x_{j+1}+1)^2-(y_j-x_{j+1}+1)^2&y_j\geq x_{j+1}\land j>0\\f_j+(y_i-x_{j+1}+1)^2&\mathrm{otherwise}\end{cases}$$这个分情况讨论很讨厌,尝试把它消灭掉:设 ,那么转移方程变为 。显然的斜率优化,
但是我不会,所以多讲一点:拆开变成 $f_i=\min_{j=1}^{i-1} f_j+(y_i+1)^2-2(y_i+1)x_{j+1}+x^2_{j+1}-g_j-K$。先将所有 加上 (不影响单调性),忽略掉 再移移项,写成 的形式,。那么 ,,,。将 看做一个点,因为 确定了,现在就要找使得 最小的 (这样能使得 最小)。由于 都是单调递增的,所以单调队列维护下凸包即可。具体地,记 为点 与 之间的斜率。首先将点 加入单调队列,接下来遍历从小到大每个位置 :
- 若单调队列大小不小于 ,设 分别为队首的第一个和第二个点,若 则 一定不劣于 ,弹出队首。重复该操作直到单调队列大小为 或 。
- 此时队首 即为最优决策 , 转移并记录选择的区间个数。
- 若单调队列大小不小于 ,设 分别为队尾的倒数第二个和倒数第一个点;若 则 一定不在下凸包上,弹出队尾。重复该操作直到单调队列大小为 或 。
- 将点 加入单调队列。
我们需要求出在代价最小的前提下最少的区间个数。不妨设 为覆盖前 条线段且代价最小时最少的区间个数 ,那么因为 显然有单调性,所以我们每次转移时只需要选择最前面的一个决策点转移即可,即第一步操作弹出队首时,将条件 改为 ,这样就保证了相同优劣的决策点,前者不会被后者所弹出(第三步的条件不需要改,因为是前面的决策点弹出后面的决策点,不影响结果)。
时间复杂度 ,其中 是二分范围,本题中为 。浮点数比较建议使用 。代码倒是不难写。
#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; } -
- 1
信息
- ID
- 10415
- 时间
- 2000ms
- 内存
- 2048MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者