1. 程式人生 > >BZOJ1805[Ioi2007]Sail船帆——線段樹+貪心

BZOJ1805[Ioi2007]Sail船帆——線段樹+貪心

題目描述

讓我們來建造一艘新的海盜船。船上有 N個旗杆,每根旗杆被分成單位長度的小節。旗杆的長度等於它被分成的小節的數目。每根旗杆上會掛一些帆,每張帆正好佔據旗杆上的一個小節。在一根旗杆上的帆可以任意排布在不同的小節中,但是每個小節上至多能掛一張帆。 在風中,帆的不同排布方式會產生不同的推動力。靠近船頭的帆比它後面的相同高度上的帆獲得的推動力少,換句話說,靠近船頭的帆的推動力由於受它後面相同高度的帆的影響而打了折扣。對於任意一張帆,它的推動力折扣等於在它後面並且和它在同一高度的帆的數目。 所有帆的任意一種位置組合的推動力折扣總和等於在該位置下所有帆的推動力折扣的總和。  前 後 這條船上有6個旗杆,從前(圖上的左邊)到後的高度分別是3,5,4,2,4和3。圖中所示的帆的位置組合的總推動力折扣是10。上圖給出了每張帆自己的推動力折扣。 任務 給定N個旗杆的高度和每個旗杆上掛的帆的數目,寫一個程式求出所有位置組合中的可能達到的最小的總推動力折扣。

輸入

第一行包含一個整數N(2 ≤ N ≤ 100 000),表示旗杆的數目。 接下來的N行每行包含兩個整數H和K(1 ≤ H ≤ 100 000, 1 ≤ K ≤ H),分別表示對應旗杆的高度及其上的帆的數目。旗杆按照從船頭到船尾的順序給出。

輸出

輸出包含一個整數,表示可以達到的最小的總推動力折扣。 注意:計算和輸出結果時使用64位整數型別(在C/C++中用long long, 在Pascal中用int64)。 評分 在25分的測試資料中,帆的不同位置組合的總數目不超過1 000 000。

樣例輸入

6
3 2
5 3
4 1
2 1
4 3
3 2

樣例輸出

10
這個樣例資料和上頁中圖示的樣例相同。
  很有思維量的一道線段樹的題。(本題有幾個貪心證明統一在最後證明) 首先能發現旗杆的順序對答案不影響(證明1),假設從下到上第i行有si個船帆,那麼答案就是Σsi。 題目就可看成每次在前hi行中選si行,使每行的si加1。 那麼我們每次貪心地在前hi行中選si最小的ki行每行+1一定會使答案最優(證明2)。 如果暴力地找前ki小的每行+1顯然不行,那麼能不能將需要加的行變成一個區間,然後線段樹上直接區間修改? 答案是可以的,我們發現對於每一步操作後的最優解中,一定有一種是滿足從下到上每行船帆數單調遞減的(證明3)。 這樣最小的ki行就是一段區間了,但考慮修改完之後是否還保持單調遞減?
假設當次修改的是[l,r],如果s[l-1]>s[l],那麼修改後依舊保持單調遞減。 如果s[l-1]=s[l]呢?這樣修改之後s[l]就比s[l-1]大了,因此要找到最左邊和s[l]相等的點L和最右端和s[l]相等的點R。 如果r<=R,即s[l]=s[r],那麼就將修改區間平移到[L,L+ki-1],否則就將修改區間拆成兩段:一段是[R+1,r],剩下的平移到從L開始處。 因為這樣修改是儘量靠前修改的,而且旗杆順序不影響答案,所以要將所有旗杆按長度從小到大排序。 證明1: 我們以兩個旗杆a,b為例,如果b,a時的最優解比a,b時的最優解更優,那麼我們將a,b調換順序並保持b,a時船帆狀態,這時的答案還是b,a時的最優解,那麼之前a,b的最優解就不是最優的了。因此無論船帆是什麼順序,都不會影響答案。 證明2: 對於每一次修改,假設不選最小的那些而是將最小的留給後面,那麼為了使答案最優,最小的那幾個一定是要選的,先選大的再選小的也就相當於先選小的再選大的。 證明3: 假設最優解中存在i>j且si>sj,因為能放到第i行的船帆一定能放到第j行,那麼一定可以將第i行比第j行多的那些都放到第j行使得si和sj大小調換且答案不變。
#include<set>
#include<map>
#include<queue>
#include<cmath>
#include<stack>
#include<cstdio>
#include<vector>
#include<bitset>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
struct miku
{
    int h;
    int s;
}a[100010];
int mn[800010];
int mx[800010];
int sum[800010];
int n,m;
ll ans;
bool cmp(miku a,miku b)
{
    return a.h<b.h;
}
void pushup(int rt)
{
    mn[rt]=min(mn[rt<<1],mn[rt<<1|1]);
    mx[rt]=max(mx[rt<<1],mx[rt<<1|1]);
}
void pushdown(int rt)
{
    if(sum[rt])
    {
        sum[rt<<1]+=sum[rt];
        sum[rt<<1|1]+=sum[rt];
        mn[rt<<1]+=sum[rt];
        mn[rt<<1|1]+=sum[rt];
        mx[rt<<1]+=sum[rt];
        mx[rt<<1|1]+=sum[rt];
        sum[rt]=0;
    }
}
void change(int rt,int l,int r,int L,int R)
{
    if(L<=l&&r<=R)
    {
        sum[rt]++;
        mx[rt]++;
        mn[rt]++;
        return ;
    }
    pushdown(rt);
    int mid=(l+r)>>1;
    if(L<=mid)
    {
        change(rt<<1,l,mid,L,R);
    }
    if(R>mid)
    {
        change(rt<<1|1,mid+1,r,L,R);
    }
    pushup(rt);
}
int query_mid(int rt,int l,int r,int k)
{
    if(l==r)
    {
        return mn[rt];
    }
    pushdown(rt);
    int mid=(l+r)>>1;
    if(k<=mid)
    {
        return query_mid(rt<<1,l,mid,k);
    }
    else
    {
        return query_mid(rt<<1|1,mid+1,r,k);
    }
}
int query_left(int rt,int l,int r,int k)
{
    if(l==r)
    {
        return l;
    }
    pushdown(rt);
    int mid=(l+r)>>1;
    if(mn[rt<<1]<=k)
    {
        return query_left(rt<<1,l,mid,k);
    }
    else
    {
        return query_left(rt<<1|1,mid+1,r,k);
    }
}
int query_right(int rt,int l,int r,int k)
{
    if(l==r)
    {
        return l;
    }
    pushdown(rt);
    int mid=(l+r)>>1;
    if(mx[rt<<1|1]>=k)
    {
        return query_right(rt<<1|1,mid+1,r,k);
    }
    else
    {
        return query_right(rt<<1,l,mid,k);
    }
}
void count_ans(int rt,int l,int r)
{
    if(l==r)
    {
        ans+=1ll*mn[rt]*(mn[rt]-1)/2;
        return ;
    }
    pushdown(rt);
    int mid=(l+r)>>1;
    count_ans(rt<<1,l,mid);
    count_ans(rt<<1|1,mid+1,r);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&a[i].h,&a[i].s);
        m=max(m,a[i].h);
    }
    sort(a+1,a+1+n,cmp);
    for(int i=1;i<=n;i++)
    {
        int res=query_mid(1,1,m,a[i].h-a[i].s+1);
        int L=query_left(1,1,m,res);
        int R=query_right(1,1,m,res);
        if(R<a[i].h)
        {
            int len=a[i].h-R;
            change(1,1,m,R+1,a[i].h);
            change(1,1,m,L,L+(a[i].s-len)-1);
        }
        else
        {
            change(1,1,m,L,L+a[i].s-1);
        }
    }
    count_ans(1,1,m);
    printf("%lld",ans);
}