NOI2016 區間 【線段樹】
阿新 • • 發佈:2018-02-24
選中 markdown bug 左右 spa mat nod const post
題目
在數軸上有 n個閉區間 [l1,r1],[l2,r2],...,[ln,rn]。現在要從中選出 m 個區間,使得這 m個區間共同包含至少一個位置。換句話說,就是使得存在一個 x,使得對於每一個被選中的區間 [li,ri],都有 li≤x≤ri。
對於一個合法的選取方案,它的花費為被選中的最長區間長度減去被選中的最短區間長度。區間 [li,ri] 的長度定義為 ri?li,即等於它的右端點的值減去左端點的值。
求所有合法方案中最小的花費。如果不存在合法的方案,輸出 ?1。
輸入格式
第一行包含兩個正整數 n,m用空格隔開,意義如上文所述。保證 1≤m≤n
接下來 n行,每行表示一個區間,包含用空格隔開的兩個整數 li 和 ri 為該區間的左右端點。
N<=500000,M<=200000,0≤li≤ri≤10^9
輸出格式
只有一行,包含一個正整數,即最小花費。
輸入樣例
6 3
3 5
1 2
3 4
2 2
1 5
1 4
輸出樣例
2
題解
比較容易想到,如果我們對所選的區間都+1,那麽一定存在某個位置的值>=m
先對區間離散化
這樣我們可以得到一個\(O(n^3)\)的算法
區間修改求最值,離散化後用線段樹優化,可以做到\(O(n^2logn)\)
為了進一步減小一個n,我們將區間按區間長度排序
從小開始逐個加入線段樹中,直到出現>=m的位置,再嘗試從小開始從線段樹中刪除,直至滿足>=m時再移動右端點
可以證明這樣做的正確性
因為區間過多不影響存在某位置>=m,通過不斷移動右端點,有解一定可以判出
設當前解為len,如果不移動左端點,右端點移動的結果一定不會比len要小
所以在滿足條件的情況下應盡量移動左端點
\(O(nlogn)\)
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long int
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
#define BUG(s,n) for (int i = 1; i <= (n); i++) cout<<s[i]<<‘ ‘; puts("");
#define ls (u << 1)
#define rs (u << 1 | 1)
using namespace std;
const int maxn = 1000005,maxm = 500005,INF = 1000000000;
inline int read(){
int out = 0,flag = 1; char c = getchar();
while (c < 48 || c > 57) {if (c == ‘-‘) flag = -1; c = getchar();}
while (c >= 48 && c <= 57) {out = (out << 3) + (out << 1) + c - ‘0‘; c = getchar();}
return out * flag;
}
struct node{int l,r;}e[maxm];
int b[maxn],bi,tot = 1,n,m,ans = INF;
int mx[4 * maxn],tag[4 * maxn];
inline bool operator <(const node& a,const node& x){
return (b[a.r] - b[a.l]) < (b[x.r] - b[x.l]);
}
int getn(int x){return lower_bound(b + 1,b + 1 + tot,x) - b;}
void pd(int u){
if (tag[u]){
mx[ls] += tag[u]; mx[rs] += tag[u];
tag[ls] += tag[u]; tag[rs] += tag[u];
tag[u] = 0;
}
}
void modify(int u,int l,int r,int L,int R,int x){
if (l >= L && r <= R){
mx[u] += x; tag[u] += x;
return;
}
pd(u);
int mid = l + r >> 1;
if (mid >= L) modify(ls,l,mid,L,R,x);
if (mid < R) modify(rs,mid + 1,r,L,R,x);
mx[u] = max(mx[ls],mx[rs]);
}
int main(){
n = read(); m = read();
for (int i = 1; i <= n; i++)
b[++bi] = e[i].l = read(),b[++bi] = e[i].r = read();
sort(b + 1,b + 1 + bi);
for (int i = 2; i <= bi; i++) if (b[i] != b[tot]) b[++tot] = b[i];
for (int i = 1; i <= n; i++)
e[i].l = getn(e[i].l),e[i].r = getn(e[i].r);
sort(e + 1,e + 1 + n);
modify(1,1,tot,e[1].l,e[1].r,1);
int l = 1,r = 1,L,R;
L = R = b[e[1].r] - b[e[1].l];
if (mx[1] >= m) ans = min(ans,R - L);
while (r <= n){
while (mx[1] >= m && l < r){
modify(1,1,tot,e[l].l,e[l].r,-1);
l++;
L = b[e[l].r] - b[e[l].l];
if (mx[1] >= m) ans = min(ans,R - L);
}
r++;
if (r > n) break;
R = b[e[r].r] - b[e[r].l];
modify(1,1,tot,e[r].l,e[r].r,1);
if (mx[1] >= m) ans = min(ans,R - L);
}
printf("%d\n",ans == INF ? -1 : ans);
return 0;
}
NOI2016 區間 【線段樹】