【JZOJ5427】【NOIP2017提高A组集训10.25】吃草[1D1D优化]

Description

New Orleans家的后院有很多片草坪,Sullivan负责清理过高的草。但是,Sullivan还有很多家务要干,于是,她想到了一个好方法。
后院总共有n片草坪,第i片草坪投影到数轴上,是一段l[i]到r[i]的闭区间,保证l[i]+r[i]是偶数,l[i]<=r[i]。Sullivan可以在整点上放0v0来把草吃掉(于是0v0变成了0π0)。如果第i片草坪覆盖了x点上的0π0(l[i]<=x<=r[i]),那么这只0π0就可以吃掉这片草坪里的草。每一片草坪的草需要且只能被一只0π0吃掉。如果一片草坪覆盖了多只0π0,Sullivan可以选择任意一只去吃草。但是,0π0吃草是有代价的,对于第i片草坪,假如吃草的0π0位于x点上,代价为abs((x-l[i])-(r[i]-x)),即0π0到草坪两端距离之差。现在,Sullivan想知道:
1.最少需要放几只0v0?
2.在放最少只数的0v0情况下,代价最小是多少?

Data Constraint

20% n,l[i],r[i]<=3000 t=0
30% n,l[i],r[i]<=300000 t=0
20% n,l[i],r[i]<=3000 t=1
30% n,l[i],r[i]<=300000 t=1

Solution

这道题为什么O(N^2)都能过???
第一问其实很简单,就是个贪心。我们将所有询问按右端点从小到大排序,然后顺序枚举,显然在当前草坪i上放肯定是越往右对以后的影响越大。所以我们每次将0v0放到当前没有被覆盖的草坪的右端点即可。
问题是第二问怎么做。
我们将草坪按x[i]+y[i]排序,设f[i]表示前i个草坪最少要放多少个0v0,并强制i点上要放一个0v0,g[i]表示在f[i]最小的情况下的最小代价。显然f数组是单调不下降的,我们转移至要枚举一个j来转移至i。j转移至i的g具体操作为:我们将j~i的草坪按(j+i)/2分成两半,(x[i]+y[i])/2在i一侧的去i,(x[i]+y[i])/2在j一侧的去j(j满足的条件必须是j~i之间的草坪都能被j或i上的0v0覆盖)很快我们就会想到i是满足决策单调性。所以我们用1D1D优化一下转移。设l[i],r[i]表示f最优值是由i转移的区间,我们每次用二分将i-1上的最优区间分成两部分。那么现在转移就是O(1)了,由于计算最优区间有个二分,所以总复杂度O(NlogN)。

Code

#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn=3e5+5,maxn1=3e5;
struct code{
    ll l,r,x;
}a[maxn],p[maxn];
ll n,bz,i,t,j,k,l,x,y,z,ans,num,f[maxn],g[maxn],s[maxn],d[maxn],r,mid,mx;
ll mi[maxn];
bool cmp(code x,code y){
    return x.l+x.r<y.l+y.r;
}
bool cmp1(code x,code y){
    return x.r<y.r;
}
bool cmp2(code x,code y){
    return x.l>y.l;
}
ll pan(ll x,ll y){
    int t=(x+y)/2;
    return (s[t]-s[x])-(d[t]-d[x])*x+(d[y]-d[t])*y-(s[y]-s[t]);
}
int main(){
    freopen("grass.in","r",stdin);freopen("grass.out","w",stdout);
    scanf("%lld%lld",&n,&bz);
    for (i=maxn1+1;i>=0;i--) mi[i]=maxn1;
    for (i=1;i<=n;i++){
        scanf("%lld%lld",&a[i].l,&a[i].r),t=a[i].l+a[i].r;
        s[t/2]+=t,d[t/2]+=2,a[i].x=t/2;
        mi[a[i].l-1]=min(a[i].r,mi[a[i].l-1]);
    }
    for (i=1;i<=maxn1;i++) s[i]+=s[i-1],d[i]+=d[i-1];
    for (i=maxn1;i>=0;i--) mi[i]=min(mi[i],mi[i+1]);
    t=0;sort(a+1,a+n+1,cmp);
    for (i=1;i<=n;i++){
        if (a[i].r==mi[0]) t=max(t,a[i].l);
        mx=max(mx,a[i].l);
        //printf("%lld %lld\n",a[i].l,a[i].r);
    }
    for (i=t;i<=mi[0];i++) 
        f[i]=1,g[i]=d[i]*i-s[i];
    p[t].l=t+1;p[t].r=maxn1;    
    j=t;
    for (i=t+1;i<=mx;i++){
        if (i>mi[0]){
            while (p[j].r<i || p[j].r<p[j].l) j++;
            f[i]=f[j]+(d[j]!=d[maxn1]);
            g[i]=g[j]+pan(j,i);
        }
        k=i-1;
        while (1){
            r=mi[k];l=min(p[k].l-1,r);
            if (f[k]<f[i]) l=r;
            while (l<r){
                mid=(l+r+1)/2;
                if (g[k]+pan(k,mid)>=g[i]+pan(i,mid)) r=mid-1;
                else l=mid;
            }
            p[k].r=l;p[i].l=l+1;p[i].r=maxn1;
            if (p[k].r<p[k].l) k--;
            else break;
        }
    }
    printf("%lld\n",f[mx]);t=0;
    if (!bz) return 0;
    for (i=1;i<=n;i++) t=max(t,a[i].x);
    k=g[mx]+s[maxn1]-s[mx]-(d[maxn1]-d[mx])*mx;
    for (i=mx+1;i<=t;i++){
        if (i>mi[0]){
            while (p[j].r<i || p[j].r<p[j].l) j++;
            f[i]=f[j]+(d[j]!=d[maxn1]);
            g[i]=g[j]+pan(j,i);
            if (f[i]>f[mx]) break;
        }
        k=min(k,g[i]+s[maxn1]-s[i]-(d[maxn1]-d[i])*i);
        r=mi[i-1];l=min(p[i-1].l-1,r);
        if (f[i-1]<f[i]) l=r;
        while (l<r){
            mid=(l+r+1)/2;
            if (g[i-1]+pan(i-1,mid)>=g[i]+pan(i,mid)) r=mid-1;
            else l=mid;
        }
        p[i-1].l=min(p[i-1].l,l);p[i-1].r=l;p[i].l=l+1;p[i].r=maxn1;
    }
     printf("%lld\n",k);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章