bzoj 5102 [POI2018]Prawnicy 題解

博客觀賞效果更佳

題意簡述

給你 nn 個區間 [li,ri][l_i,r_i],選出 恰好 kk 個,使得交集最大。輸出最長的長度和方案。

注:區間 [l,r][l,r] 的長度被定義爲 rlr-l

1kn1051\le k\le n\le 10^5

思路框架

若干的區間的交集,顯然,左端點是所有左端點的最大值,右端點是所有右端點的最小值。

然後我們枚舉左端點的最大值。怎麼枚舉呢?按左端點從小到大排序,枚舉到 ii 表示只能用 ii 之前的區間。那麼所有的左端點就都 li\le l_i 了。

然後這個時候我們怎麼選最大值呢?

  1. 我們只能在 1i1\sim i 中選
  2. 選擇 kk 個使得最小值最大

怎麼選擇 kk 個使得最小值最大呢?那肯定是從大到小排序之後選前 kk 個啊!然後這時候最大的最小值,就是從大到小之後排第 kk 位的數。那麼我們現在就是要支持動態插入(無刪除)的第 kk 大。可以用對頂堆做。

對頂堆如何做

維護一個小根堆(堆頂是最小值),和一個大根堆(堆頂是最大值)。我們默認把數字插入到小根堆中,如果小根堆中的數字個數 >k>k,那麼我們把小根堆中的 最小值 放到大根堆裏。

那麼有一個顯然易證的性質:大根堆中的所有數,任何時候都 \le 小根堆中的所有數。也就是,大根堆的堆頂 \le 小根堆的堆頂。

而且我們還保證了小根堆中只有 kk 個數。那麼這 kk 個數 \ge 當前的其它所有數,顯然,這 kk 個數字就是前 kk 大。求出這 kk 個數中的最小值(小根堆頂),就是當前的第 kk 大。

這個辦法也可以用於求中位數 (也就是 kk 不一定靜態,單調遞增的情況也可以求)

代碼

#include <bits/stdc++.h>
using namespace std;
namespace Flandre_Scarlet
{
    #define N 1666666
    #define F(i,l,r) for(int i=l;i<=r;++i)
    #define D(i,r,l) for(int i=r;i>=l;--i)
    #define Fs(i,l,r,c) for(int i=l;i<=r;c)
    #define Ds(i,r,l,c) for(int i=r;i>=l;c)
    #define MEM(x,a) memset(x,a,sizeof(x))
    #define FK(x) MEM(x,0)
    #define Tra(i,u) for(int i=G.Start(u),v=G.To(i);~i;i=G.Next(i),v=G.To(i))
    #define p_b push_back
    #define sz(a) ((int)a.size())
    #define all(a) a.begin(),a.end()
    #define iter(a,p) (a.begin()+p)
    int I()
    {
        int x=0;char c=getchar();int f=1;
        while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar();
        while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
        return (x=(f==1)?x:-x);
    }
    void Rd(int cnt,...)
    {
        va_list args; va_start(args,cnt);
        F(i,1,cnt) {int* x=va_arg(args,int*);(*x)=I();}
        va_end(args);
    }
 
    struct node{int l,r,id;} a[N];
    bool operator<(node a,node b){return a.l<b.l;}
 
    int n,k;
    void Input()
    {
        Rd(2,&n,&k);
        F(i,1,n) a[i]=(node){I(),I(),i};
    }
 
    priority_queue<int,vector<int>,greater<int> > Q1; // 小根堆
    priority_queue<int> Q2; // 大根堆
    void Soviet()
    {
        sort(a+1,a+n+1);
        int Max=-0x3f3f3f3f,l,r;
        F(i,1,n)
        {
            Q1.push(a[i].r);
            if (sz(Q1)>k) 
            // 其實這裏理論上應該是個 while 循環
            // 然而實際上我們每次只會新加入一個數,那麼 sz(Q1) 頂多等於 k+1,彈一次即可
            // 寫中位數的話,這裏要改成 while !
            {
                Q2.push(Q1.top()); Q1.pop();
            }
             
            if (i>=k) // 這裏注意判一下 i>=k,很重要,會出錯! (我就掛在這的QaQ
            {
                int rr=Q1.top(),ll=a[i].l;
                if (rr-ll>Max)
                {
                    Max=rr-ll; l=ll; r=rr;
                }
            }
        }
        printf("%d\n",Max);
        int cnt=0;
        F(i,1,n) 
        {
            if (a[i].l<=l and r<=a[i].r) 
            {
                printf("%d ",a[i].id);
                ++cnt;
            }
            if (cnt==k) break;
        }
    }
 
    #define Flan void
    Flan IsMyWife()
    {
        Input();
        Soviet();
    }
}
int main()
{
    Flandre_Scarlet::IsMyWife();
    getchar();getchar();
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章