USACO 19 FEB Mowing Mischief P 题解

题目传送门

题目大意: 给出 n n n 个平面上的点,你要选定若干个点,满足 x i − 1 < x i , y i − 1 < y i x_{i-1}<x_i,y_{i-1}<y_i xi1<xi,yi1<yi,然后在选出点最多的前提下使相邻的两个点围成的矩形面积之和最小。

题解

由于要使选出点最多,所以先将 x x x 排序然后跑个最长上升子序列,记以 i i i 结尾的 l i s lis lis l i l_i li,那么要将点按 l i l_i li 分组,每组内的点只能从上一组的点转移过来。

由于一组内的点的 l i l_i li 相同,所以有一个性质:不存在同组内的两个点满足 x j < x i , y j < y i x_j<x_i,y_j<y_i xj<xi,yj<yi,否则 l i l_i li 肯定至少比 l j l_j lj 1 1 1。而由于我们按 x x x 排了序,所以对于同组内 j < i j<i j<i,一定有 x j < x i , y j > y i x_j<x_i,y_j>y_i xj<xi,yj>yi

考虑暴力dp,设 f i f_i fi 表示走到 i i i 点的最小矩形面积,有:
f i = min ⁡ { f j + ( x i − x j ) ( y i − y j ) } f_i=\min\{f_j+(x_i-x_j)(y_i-y_j)\} fi=min{ fj+(xixj)(yiyj)}

若对 i i i 来说,从 j j j 转移优于从 k k k 转移,那么有:
f j + x j y j + x i y i − x i y j − x j y i < f k + x k y k + x i y i − x i y k − x k y i x i ( y j − y k ) + y i ( x j − x k ) > f j + x j y j − f k − x k y k \begin{aligned} f_j+x_jy_j+x_iy_i-x_iy_j-x_jy_i&<f_k+x_ky_k+x_iy_i-x_iy_k-x_ky_i\\ x_i(y_j-y_k)+y_i(x_j-x_k)&>f_j+x_jy_j-f_k-x_ky_k\\ \end{aligned} fj+xjyj+xiyixiyjxjyixi(yjyk)+yi(xjxk)<fk+xkyk+xiyixiykxkyi>fj+xjyjfkxkyk

j < k j<k j<k 时,根据上面的性质,就有 y j − y k > 0 , x j − x k < 0 y_j-y_k>0,x_j-x_k<0 yjyk>0,xjxk<0,假如将 x i , y i x_i,y_i xi,yi 看成变量,那么满足这个式子的 x i , y i x_i,y_i xi,yi 就一定在某条斜率为正的直线下方(手玩一下 x i x_i xi 增大时 y i y_i yi 的变化就好了)。

i i i 增大时,又根据性质, x i x_i xi 一定增大, y i y_i yi 一定减小,不难发现这个点一定还在那条直线的上方,也就是说, j j j 依然比 k k k 优。

类似的,当 j > k j>k j>k 时, x i , y i x_i,y_i xi,yi 就在某条直线的上方,当 i i i 减小时,依然存在 j j j k k k 优。

有了单调性,我们就可以分治了。对于一组内的某个点,上一组中能转移到它的一定是一个区间,考虑用线段树分治将这个点的询问放到这个区间上。

然后对于线段树上的每个点,我们就可以愉快的再利用单调性跑分治了,具体可以参考代码。

代码如下:

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 200010

int n,M;
struct point{
    
    int x,y;}a[maxn];
bool cmp(point x,point y){
    
    return x.x<y.x;}
template<class TY>inline void chkmax(TY &x,TY y){
    
    if(y>x)x=y;}
template<class TY>inline void chkmin(TY &x,TY y){
    
    if(y<x)x=y;}
struct TREE{
    
    
	int tr[1000010];
	void change(int x,int y){
    
    for(;x<=M;x+=(x&-x))chkmax(tr[x],y);}
	int getmax(int x){
    
    int re=0;for(;x;x-=(x&-x))chkmax(re,tr[x]);return re;}
}tr;
int lis[maxn];
vector<int> d[maxn],s[maxn<<1];
long long f[maxn];
void insert(int now,int l,int r,int x,int la){
    
    
	if(a[x].x<a[d[la][l]].x||a[x].y<a[d[la][r]].y)return;//整个区间都不能转移到x,就退出
	if(a[x].x>a[d[la][l]].x&&a[x].y>a[d[la][l]].y//整个区间都能转移到x
		&&a[x].x>a[d[la][r]].x&&a[x].y>a[d[la][r]].y){
    
    s[now].push_back(x);return;}
	int mid=l+r>>1;
	insert(now<<1,l,mid,x,la);insert(now<<1|1,mid+1,r,x,la);
}
void solve(int l,int r,int L,int R,int now,int la){
    
    
	if(L>R)return;
	//分治到询问的[L,R]区间,此时上一组中能转移到这个区间的区间为[l,r]
	long long mi=1e18;int pos,mid=L+R>>1;//下面是找到mid的最优转移点
	for(int i=l;i<=r;i++){
    
    
		if(f[d[la][i]]+1ll*(a[s[now][mid]].x-a[d[la][i]].x)*(a[s[now][mid]].y-a[d[la][i]].y)<mi)
			mi=f[d[la][i]]+1ll*(a[s[now][mid]].x-a[d[la][i]].x)*(a[s[now][mid]].y-a[d[la][i]].y),pos=i;
	}
	chkmin(f[s[now][mid]],mi);
	//由于在pos左边的点都不如pos优,所以当mid减小时这些点依然不如pos优
	//此时对于[L,mid)这个区间而言,只需要考虑pos右边的点是否能转移即可,(mid,R]类似。
	solve(l,pos,mid+1,R,now,la);solve(pos,r,L,mid-1,now,la);
}
void go(int now,int l,int r,int la){
    
    
	solve(l,r,0,s[now].size()-1,now,la);
	s[now].clear();
	if(l==r)return;int mid=l+r>>1;
	go(now<<1,l,mid,la);go(now<<1|1,mid+1,r,la);
}

int main()
{
    
    
	scanf("%d %d",&n,&M);
	for(int i=1;i<=n;i++)
		scanf("%d %d",&a[i].x,&a[i].y);
	a[n+=2]=(point){
    
    M,M};
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;i++){
    
    
		lis[i]=tr.getmax(a[i].y)+1;
		tr.change(a[i].y+1,lis[i]);
		d[lis[i]].push_back(i);
	}
	memset(f,63,sizeof(f)),f[1]=0;
	for(int i=2;i<=n;i++){
    
    
		if(!d[i].size())break;
		for(int j:d[i])insert(1,0,d[i-1].size()-1,j,i-1);
		go(1,0,d[i-1].size()-1,i-1);
	}
	printf("%lld",f[n]);
}
/*
0 10
*/

猜你喜欢

转载自blog.csdn.net/a_forever_dream/article/details/113172209