【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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章