Luogu P5244 [USACO2019Feb Platinum] Mowing Mischief (动态规划、决策单调性)

题目链接

https://www.luogu.com.cn/problem/P5244

题解

首先求出 LIS. 根据 LIS 的值我们可以对整个点集分层,每一层内进行 DP. 将每层的点按 \(x_i\) 从小到大排序,那么显然一层内的 \(y_i\) 是递减的。设第 \(d\) 层的点集为 \(L_d\).
那么上一层某点能转移到的该层的点是一段区间,能转移到该层某点的上一层的点也是一段区间。设能转移到该层 (第 \(d\) 层) \(i\) 点的上一层的点区间为 \([l_i,r_i]\)\([l_i,r_i]\in L_{d-1}\), 则转移方程为 \(f[i]=\min_{j\in [l_i,r_i]}(f(d-1,j)+(x_i-x_j)(y_i-y_j))\).
现在我们证明这个 DP 有决策单调性: 设同一层的两点 \((x_1,y_1),(x_2,y_2)\in L_{d-1}\) 满足\(x_1<x_2,y_1>y_2\), 那么转移到的点 \(x\) 越大时,\((x_2,y_2)\) 相对于 \((x_1,y_1)\) 越优。设二者的 \(f\) 值分别为 \(f_1,f_2\), 转移到的点为 \((x,y)\), 二者作差可得 \(f_1+(x-x_1)(y-y_1)-f_2-(x-x_2)(y-y_2)\), 去掉与 \(x,y\) 无关的部分后整理得 \(x(y_2-y_1)+y(x_2-x_1)\). 由于 \(x\) 单调增,\((y_2-y_1)\) 为负数,\(y\) 单调减,\((x_2-x_1)\) 为正数,故该式值单调减,因此一定存在一个位置满足它前面的 \((x_2,y_2)\) 更优,后面的 \((x_1,y_1)\) 更优 (前、后都指按 \(x\) 从小到大排序),证毕。
假设转移没有区间限制,就可以直接用传统的方法优化到 \(O(n\log n)\). 但是这里有转移的区间限制,于是一个直接的方法是用线段树维护,对上一层建线段树,线段树的每个区间使用传统的方法跑一遍决策单调性,时间复杂度 \(O(n\log^2 n)\).
但是官方题解给了个更妙的做法: 由于这里 \(l_i\)\(r_i\) 都是单调递增的,我们可以把上一层分为若干个区间,满足每个 \([l_i,r_i]\) 只和不超过 \(2\) 段区间有交,且和每一段有交的区间的交都是一段前缀或者后缀。具体的方法是直接对于当前待划分的区间的左端点 \(l\) 选取左端点不超过 \(l\)\([l_i,r_i]\) 中最大的 \(r_i\) 作为划分出的第一个区间的右端点就可以了。那么我们对划分出的每段区间分别从前往后和从后往前跑一遍传统方法的决策单调性,就可以完成 DP 了。
时间复杂度 \(O(n\log n)\).

代码

#include<bits/stdc++.h>
#define llong long long
#define mkpr make_pair
#define riterator reverse_iterator
#define pii pair<int,int>
using namespace std;

inline int read()
{
    int x = 0,f = 1; char ch = getchar();
    for(;!isdigit(ch);ch=getchar()) {if(ch=='-') f = -1;}
    for(; isdigit(ch);ch=getchar()) {x = x*10+ch-48;}
    return x*f;
}

const int mxN = 2e5+2;
const int mxMX = 1e6;
const llong INF = 1e13;
struct Point
{
    int x,y,w;
    Point() {}
    Point(int _x,int _y) {x = _x,y = _y;}
} a[mxN+3];
int bit[mxMX+3];
int lb[mxN+3],rb[mxN+3];
int segr[mxN+3];
llong f[mxN+3];
pii que[mxN+3];
vector<int> trans[mxN+3][2];
int n,m,segn; llong mx;

bool cmp_xy(Point x,Point y) {return x.x<y.x||(x.x==y.x&&x.y<y.y);}
bool cmp_w(Point x,Point y) {return x.w<y.w||(x.w==y.w&&x.x<y.x);}

void modify(int u,int x) {u++; while(u<=mx+1) {bit[u] = max(bit[u],x); u+=(u&(-u));}}
int query(int u) {u++; int ret = 0; while(u) {ret = max(ret,bit[u]); u-=(u&(-u));} return ret;}

llong calc(int i,int j) {return f[i]+1ll*(a[j].x-a[i].x)*(a[j].y-a[i].y);}

int main()
{
    scanf("%d%lld",&n,&mx);
    for(int i=1; i<=n; i++) scanf("%d%d",&a[i].x,&a[i].y); a[++n] = Point(0,0),a[++n] = Point(mx,mx);
    sort(a+1,a+n+1,cmp_xy);
    for(int i=1; i<=n; i++)
    {
        a[i].w = query(a[i].y)+1;
        modify(a[i].y,a[i].w);
        m = max(m,a[i].w);
    }
    sort(a+1,a+n+1,cmp_w);
    llong ans = INF; for(int i=1; i<=n; i++) f[i] = INF; f[1] = 0ll;
    for(int l=2,pl=1,pr=1; l<=n; l++)
    {
        int r = l; while(r<n&&a[r+1].w==a[l].w) {r++;}
        for(int i=l,j=pl; i<=r; i++)
        {
            while(j<pr&&a[j+1].x<=a[i].x) {j++;}
            rb[i] = j;
        }
        for(int i=r,j=pr; i>=l; i--)
        {
            while(j>pl&&a[j-1].y<=a[i].y) {j--;}
            lb[i] = j;
        }
        segn = 0; segr[0] = pl-1;
        for(int l2=pl,j=l; l2<=pr; l2++)
        {
            segn++; int r2 = l2;
            while(j<=r&&lb[j]<=l2)
            {
                r2 = max(r2,rb[j]);
                if(rb[j]>=l2) {trans[segn][0].push_back(j);}
                if(lb[j]<l2) {trans[segn-1][1].push_back(j);}
                j++;
            }
            segr[segn] = r2;
            l2 = r2;
            if(l2==pr)
            {
                while(j<=r)
                {
                    trans[segn][1].push_back(j);
                    j++;
                }
            }
        }
        for(int i=1; i<=segn; i++)
        {
            int hd = 1,tl = 0;
            for(int j=segr[i-1]+1,k=0; j<=segr[i]; j++)
            {
                while(tl>=hd&&calc(que[tl].first,que[tl].second)>=calc(j,que[tl].second)) {tl--;}
                if(tl<hd) {que[++tl] = mkpr(j,r);}
                else
                {
                    int left = l-1,right = que[tl].second;
                    while(left<right)
                    {
                        int mid = (left+right+1)>>1;
                        if(calc(que[tl].first,mid)>=calc(j,mid)) {left = mid;}
                        else {right = mid-1;}
                    }
                    if(left>=l) {que[++tl] = mkpr(j,left);}
                }
                while(k<trans[i][0].size()&&rb[trans[i][0][k]]==j)
                {
                    int pos = trans[i][0][k];
                    while(tl>hd&&calc(que[tl].first,pos)>=calc(que[tl-1].first,pos)) {tl--;}
                    f[pos] = min(f[pos],calc(que[tl].first,pos));
                    k++;
                }
            }
            if(trans[i][1].size()==0) continue;
            hd = 1,tl = 0;
            for(int j=segr[i],k=trans[i][1].size()-1; j>segr[i-1]; j--)
            {
                while(tl>=hd&&calc(que[tl].first,que[tl].second)>=calc(j,que[tl].second)) {tl--;}
                if(tl<hd) {que[++tl] = mkpr(j,l);}
                else
                {
                    int left = que[tl].second,right = r+1;
                    while(left<right)
                    {
                        int mid = left+(right-left>>1);
                        if(calc(que[tl].first,mid)>=calc(j,mid)) {right = mid;}
                        else {left = mid+1;}
                    }
                    if(right<=r) {que[++tl] = mkpr(j,right);}
                }
                while(k>=0&&lb[trans[i][1][k]]==j)
                {
                    int pos = trans[i][1][k];
                    while(tl>hd&&calc(que[tl].first,pos)>=calc(que[tl-1].first,pos)) {tl--;}
                    f[pos] = min(f[pos],calc(que[tl].first,pos));
                    k--;
                }
            }
        }
        for(int i=0; i<=segn; i++) trans[i][0].clear(),trans[i][1].clear();
        pl = l,l = pr = r;
    }
    printf("%lld\n",f[n]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值