51nod1672&HDU5700--區間交(線段樹)區間枚舉小技巧

題目鏈接https://www.51nod.com/Challenge/Problem.html#problemId=1672

小A有一個含有n個非負整數的數列與m個區間,每個區間可以表示爲li,ri。

它想選擇其中k個區間, 使得這些區間的交的那些位置所對應的數的和最大。(是指k個區間共同的交,即每個區間都包含這一段,具體可以參照樣例)

在樣例中,5個位置對應的值分別爲1,2,3,4,6,那麼選擇[2,5]與[4,5]兩個區間的區間交爲[4,5],它的值的和爲10。

輸入

第一行三個數n,k,m(1<=n<=100000,1<=k<=m<=100000)。
接下來一行n個數ai,表示小A的數列(0<=ai<=10^9)。
接下來m行,每行兩個數li,ri,表示每個區間(1<=li<=ri<=n)。

輸出

一行表示答案

輸入樣例

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

輸出樣例

10

很顯然,我們可以直接枚舉區間[l,r],那麼答案就是其中的某個區間。而很顯然,我們可以直接枚舉左端點l,然後對於在這個區間內的小區間它絕對滿足L<=l並且R>=l的,也就是說對於滿足條件的小區間,我們將他們扔到線段樹操作,如果這個區間被大於等於k個區間覆蓋,那麼我們需要找到對於左端點l而言被k個區間覆蓋的最大的R(這樣就可以使得覆蓋的元素更多和也更大),也就是找當前符合區間的最大R。那麼直接用權值線段樹就可以找到該位置了。但有一個問題就是我們怎麼判斷哪個區間滿足條件,最暴力的方法就是在枚舉l的同時對每個區間枚舉,然後判斷。。。果斷T飛,實際上我們可以注意到的是,我們的l是從小到大枚舉的,那麼我們對每個區間的左端點排個序就好了,然後記錄一下每個左端點所對應的的右端點的值,以及右端點對應的右端點的值:

for (int i=1; i<=m; i++){
      int l,r;
      scanf ("%d%d",&l,&r);
      st[l].push_back(r);ed[r].push_back(r);
}

那麼我們從1到n枚舉區間的時候就可以很快地判斷了。我們每次枚舉l的時候,在線段樹中插入以l爲左端點的區間(實際上只放了該區間的右端點),然後刪除已l爲右端點的區間及update(st[l],1),update(ed[l],-1),這樣的區間枚舉就是O(n)的複雜度了,在權值線段樹中找到最大的R然後用前綴和相減一下就是k個區間以l爲左端點的最大元素和了。講得不太好QAQ。。。。代碼混着文字說明應該更容易理解。

以下是AC代碼:

#include <bits/stdc++.h>
using namespace std;

#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define ls rt<<1
#define rs rt<<1|1
#define ll long long

const int mac=1e5+10;

int tree[mac<<2];

void update(int l,int r,int rt,int pos,int val)
{
    if (l==r){
        tree[rt]+=val;
        return;
    }
    int mid=(l+r)>>1;
    if (mid>=pos) update(lson,pos,val);
    else update(rson,pos,val);
    tree[rt]=tree[ls]+tree[rs];
}

int query(int l,int r,int rt,int val)
{
    if (l==r) return l;
    int mid=(l+r)>>1;
    if (tree[rs]>=val) return query(rson,val);
    else return query(lson,val-tree[rs]); 
}

vector<int>st[mac],ed[mac];
ll sum[mac];

int main()
{
    int n,k,m;
    while (scanf ("%d%d%d",&n,&k,&m)!=EOF){
        for (int i=1; i<=n; i++){
            st[i].clear();ed[i].clear();
            int x;
            scanf ("%d",&x);
            sum[i]+=sum[i-1]+x;
        }
        for (int i=1; i<=m; i++){
            int l,r;
            scanf ("%d%d",&l,&r);
            st[l].push_back(r);ed[r].push_back(r);
        }
        ll ans=0;
        for (int i=1; i<=n; i++){
            for (int j=0; j<st[i].size(); j++)
                update(1,n,1,st[i][j],1);
            if (tree[1]>=k){
                int pos=query(1,n,1,k);
                ans=max(ans,sum[pos]-sum[i-1]);
            }
            for (int j=0; j<ed[i].size(); j++)
                update(1,n,1,ed[i][j],-1);
        }
        memset(tree,0,sizeof tree);
        printf ("%lld\n",ans);
    }
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章