算法 {区间选点的最大匹配}

算法 {区间选点的最大匹配}

区间选点的最大匹配

定義

给定区间集合A(每个区间[l,r]都是整数), 你需要从實數域裡選擇一個點集P 使得存在一個A的子集SA, 滿足: 存在雙射P<->SA 使得P中的點x包含於SA的區域[l,r]裡 (即l<=x<=r);
求點集P的大小的最大值; (簡之 讓點集P盡可能的大 使得P裡的每個點都能A裡的區間進行一一匹配)
. 比如, A: {[1,1], [2,2], [1,2]}, 那麼答案為2, 選擇P={1,2}, SA={[1,1] [2,2]} (SA不唯一);

算法

{ 錯誤

將A進行左端點排序, 維護一個全局變量pos 假如l,r >= 0 那麼讓pos=-1(表示已經選擇的P集合 都是<=pos的點), 對於當前區間[l,r], 如果r >= max(pos+1,l) 則選擇max(pos+1,l)這個點與當前區間匹配;

long long L = -1;
int ANS = 0;
for( auto & i : A){
    auto lef = i.first, rig = i.second;
    if( rig >= max( L+1, lef)){
        L = max(L+1,lef); // 選擇L與當前區間`i`匹配;
        ++ ANS;
        break;
    }
}

這個貪心策略 其實是錯誤的…
比如[1,1], [1,3], [2,2], 你的答案是2, 實際答案是3;

}

正確的做法 也是貪心, 但沒上面的貪心 那麼簡單…
假如0<=l/r<=1e18, 遍歷for( pos : [0,1,...,1e18]):
. 1:[把A中的所有的右端點<pos的區間 給刪除掉];
. 2:[在A中的所有的
左端點<=pos的區間裡 選擇一個右端點最小的區間T
];
. (此時T一定包含pos) 那麼pos-T就是一個匹配 當然將T從A中刪除掉;
雖然也是貪心 但要比上面的做法複雜很多;
這裡就不證明了 畢竟貪心題…; 簡單證明下, 此時[0,...pos-1]不用管 他們已經匹配了 因此1:是合理的, 對於2: 選擇出來的這個T 比如他是[l,r], 他的[l,pos]子區間不用管 因為是在pos的左側, 關鍵是看[pos+1, r]這個子區間 由於r是滿足(覆蓋pos的最小的右端點) 也就是意味著 在當前所有滿足(覆蓋pos)的區間裡 T這個區間的[pos+1, r]子區間 是最小的, 也就是意味著 他給接下來的點[pos+1, pos+2,...] 餘出了更大的空間和可能性 因為剩下的區間 他們的[pos+1, R]中的R>r都很大 即選擇性就大, 也就是選擇的這個T 是最劣的, 而剩下的區間 都是優秀的;

這是超時的 要把這個pos的for循環給優化掉, 如果你把他優化為: 讓pos遞增的遍歷A的交集, 那麼也是超時的 因為A的區間可以是[0,1e18];
因為最終答案 pos只有N個, 因此嘗試讓每一次的pos遍歷的 就是答案, 即最多遍歷N次;

1:裡面的操作 是要找A裡面的右端點, 而2:的操作 找的是A裡面的左端點, 他倆是無法協調的 因為你如果對A進行排序 要麼是對左端點 要麼是對右端點, 是很矛盾 互相排斥的邏輯;
. 此時 一個重要的跳躍性思維來了, 我們把1:, 2:給合併掉 因為沒辦法了 他倆本身是水火不容的, 由於1:操作很簡單(就是刪除一些特定的區間) 於是把1:操作 融合到2:裡面去, 組成一個新的操作:[在A裡所有的左端點<=pos的區間集合S裡 把S中的所有(右端點<pos的區間)都刪除掉 得到S1集合, 在S1集合裡 獲取右端點最小的區間T];
集合S只是個中介 關鍵是要得到S1集合; 此時有性質: 令SS為當前pos的集合S, 令SSS為任意>pos的集合S, 令SS1為當前pos的集合S1, 令SSS1為任意>pos的集合S1, 那麼會有 S S ⊂ S S S , S S 1 ⊂ S S S 1 SS \subset SSS, SS1 \subset SSS1 SSSSS,SS1SSS1, 而又有 S 1 ⊂ S S1 \subset S S1S, 因此 我們可以直接 全局的來同時維護{S,S1}集合;
heap為一個pair<右端點,左端點>遞增排序的堆, 將區間A按照左端點排序, 令ind=0, 對於當前pos, 他要滿足: 所有的A[>=ind].左端點 > pos 意味著pos所匹配的區間 一定是[0...ind-1]範圍裡的; heap存儲的是區間 都是A[0...ind-1]裡面的區間 且滿足 A[0...ind-1]裡的所有(右端點>=pos的)區間 一定是在heap裡面的;
1: 將heap裡 (右端點<pos的區間(一定在堆頂)) 刪除;
2: 此時heap的堆頂元素 如果存在 他就是和pos所匹配的區間, 將他刪除 記錄答案;
3: 更新pos;
. IF(heap不是空的): 執行++pos; 比如A= [ [1,2], [1,3]]pos=1時 heap裡面此時[1,3] 此時ind=2, 那麼就繼續讓pos ++;
. @ELSE: @IF(ind == N):[算法結束]; @ELSE: 令pos = A[ind].左端點; 比如A= [ [1,1], [5,5]], 當pos=1 此時heap為空, 讓pos=2沒有必要 讓他直接跳到5;
4: 更新heap; 因為此時pos更新了 因此heap也需要更新, 遍歷while( ind < N) 只要A[ind].左端點 <= posA[ind ++]放到heap裡;

代碼

{
    __A: 區間的集合;
    __N: __A的長度;
    對`__A`進行*左端點*的排序;
    int64_t __pos = 0; // 點的坐標
    priority_queue< pair<int64_t,int64_t>, vector<pair<int64_t,int64_t>>, greater<>> __heap; // 他的元素是`[右端點,左端點]`;
    for( int __ind = 0;;){
        //>< `A[ind].first(左端點) > pos`;
        while( __heap.empty()==false && __heap.top().first<__pos){ // 堆頂元素的區間*右端點* 為`heap.top().first`;
            __heap.pop();
        }
        if( false == __heap.empty()){
            //>< 點`pos` 與 `Q.top()`所對應的區間 匹配;
            __heap.pop();
        }

        if( __heap.empty()){
            if( __ind == __N){ break;}
            __pos = A[__ind].first;
        }
        else{
            ++ __pos;
        }
        while( __ind<__N && A[__ind].first<=__pos){
            __heap.push( {A[__ind].second, A[__ind].first});  ++ __ind;
        }
    }
}

例題

@LINK: https://atcoder.jp/contests/abc325/tasks/abc325_d;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值