【洛谷5244】[USACO19FEB] Mowing Mischief P(线段树分治+决策单调性)
- 一张\(T\times T\)的网格图,给定\(n\)个关键点,可以从中选出若干个,满足每个点都在前一个点的右上方。
- 要求在选出点数尽可能多的前提下,求出相邻两关键点(包括第一个关键点与\((0,0)\),最后一个关键点与\((T,T)\))所夹矩形面积之和的最小值。
- \(n\le2\times10^5,T\le10^6\),保证每行每列最多只有一个关键点
\(LIS\)分层
首先按照横坐标排序,那么我们能选出的点的纵坐标形成一个上升子序列。
现在要求选出点数尽可能多,就是要求最长上升子序列。
我们记\(f_i\)表示以\(i\)为结尾的最长上升子序列长度,则根据\(LIS\)问题的经典理论,我们必须要对于每个\(f_i\)选出恰好一个点\(w_{f_i}\),满足\(w_{f_i}\)在\(w_{f_i-1}\)的右上方。
因此对于这道题我们可以分层转移。
线段树分治
考虑\(f_i\)相同的若干个点,由于我们之前已经按横坐标排过序了,因此它们的纵坐标肯定递减(否则,它们之间就存在转移关系,\(f_i\)不可能相同)。
而对于\(p_i\),上一层的一个点\(p_j\)能转移到\(p_i\),需要满足\(p_j\)的两维坐标都小于\(p_i\),因此转移范围应该是上一层的所有点中的一段区间。
于是我们利用线段树分治,把\(p_i\)扔到能转移到它的区间在线段树中对应的节点上,那么这个限制就被化掉了。
这样一来,所有上一层的转移点都能自由地转移到这一层的所有点,问题就简化了许多。
决策单调性
比较两个转移点\(p_j,p_k(j<k)\),判断何时\(j\)优于\(k\):
\[f_j+(x_i-x_j)(y_i-y_j)<f_k+(x_i-x_k)(y_i-y_k)\\
y_i(x_k-x_j)+x_i(y_k-y_j)<f_k+x_ky_k-f_j-x_jy_j
\]
把\(y_i,x_i\)看成变量,也就是说:
\[y_i<\frac{y_j-y_k}{x_k-x_j}x_i+\frac{f_k+x_ky_k-f_j-x_jy_j}{x_k-x_j}
\]
即,使得\(j\)优于\(k\)的\((x_i,y_i)\)位于一条斜率为正的直线右下方的一个半平面,具有决策单调性。
但要注意,这里的决策单调性是这一层越靠后的位置,越可能选择上一层靠前的位置
代码:\(O(nlog^2n)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 200000
#define M 1000000
#define LL long long
#define INF (LL)1e18
using namespace std;
int n,m;struct P {int x,y;I bool operator < (Con P& o) Con {return x<o.x;}}p[N+5];
vector<P> w[N+5];vector<LL> f[N+5];
namespace FastIO
{
#define FS 100000
#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
char oc,FI[FS],*FA=FI,*FB=FI;
Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
}using namespace FastIO;
namespace DP//决策单调性优化DP
{
P p[N+5],q[N+5];LL f[N+5],ans[N+5];
I void Solve(CI l,CI r,CI L,CI R)//分治决策单调性
{
#define Calc(i) (f[i]+1LL*(q[mid].x-p[i].x)*(q[mid].y-p[i].y))
if(l>r) return;RI mid=l+r>>1,g=L;for(RI i=L+1;i<=R;++i) Calc(g)>Calc(i)&&(g=i);//枚举最优决策点
ans[mid]=Calc(g),Solve(l,mid-1,g,R),Solve(mid+1,r,L,g);//这一层越靠后的位置,越可能选择上一层靠前的位置
}
}
int sz;class SegmentTree
{
private:
#define PT CI l=0,CI r=sz-1,CI rt=1
#define LT l,mid,rt<<1
#define RT mid+1,r,rt<<1|1
vector<int> V[N<<2];
public:
I void K(CI L,CI R,CI p,PT)//把区间扔到线段树上
{
if(L<=l&&r<=R) return (void)V[rt].push_back(p);
RI mid=l+r>>1;L<=mid&&(K(L,R,p,LT),0),R>mid&&(K(L,R,p,RT),0);
}
I void Solve(CI id,PT)//线段树分治
{
if(!V[rt].empty())
{
RI i,sz;for(i=l;i<=r;++i) DP::p[i-l+1]=w[id-1][i],DP::f[i-l+1]=f[id-1][i];
for(sz=V[rt].size(),i=0;i^sz;++i) DP::q[i+1]=w[id][V[rt][i]];
for(DP::Solve(1,sz,1,r-l+1),i=0;i^sz;++i) f[id][V[rt][i]]=min(f[id][V[rt][i]],DP::ans[i+1]);V[rt].clear();//与原有答案比较
}
if(l==r) return;RI mid=l+r>>1;Solve(id,LT),Solve(id,RT);
}
}S;
struct TreeArray
{
int a[M+5];I void U(RI x,CI v) {W(x<=m&&a[x]<v) a[x]=v,x+=x&-x;}
I int Q(RI x) {RI t=0;W(x) t=max(t,a[x]),x-=x&-x;return t;}
}T;
I int FX(Con vector<P>& v,CI x) {RI l=0,r=v.size()-1,mid;W(l^r) v[mid=l+r+1>>1].x<x?l=mid:r=mid-1;return l;}//二分最后的横坐标小于x的位置
I int FY(Con vector<P>& v,CI y) {RI l=0,r=v.size()-1,mid;W(l^r) v[mid=l+r-1>>1].y<y?r=mid:l=mid+1;return r;}//二分第一个横坐标小于y的位置
int main()
{
RI i,j,x;for(read(n),read(m),i=1;i<=n;++i) read(p[i].x),read(p[i].y);
RI Mx=0;for(sort(p+1,p+n+1),i=1;i<=n;++i) T.U(p[i].y,x=T.Q(p[i].y)+1),w[x].push_back(p[i]),Mx=max(Mx,x);//按横坐标排序,树状数组求LIS并分层
for(w[0].push_back((P){0,0}),f[0].push_back(0),i=1;i<=Mx;S.Solve(i++))//分层转移
for(sz=w[i-1].size(),x=w[i].size(),j=0;j^x;++j) S.K(FY(w[i-1],w[i][j].y),FX(w[i-1],w[i][j].x),j),f[i].push_back(INF);//求出转移范围,利用线段树分治化掉转移限制
LL t=INF;for(j=0;j^x;++j) t=min(t,f[Mx][j]+1LL*(m-w[Mx][j].x)*(m-w[Mx][j].y));return printf("%lld\n",t),0;//枚举最后一层点求答案
}
待到再迷茫时回头望,所有脚印会发出光芒