[NOI2016]区间
题目大意:
给你N个区间,选出M个,
使这m个区间共同包含至少一个位置,且使里面区间长度最长减
区间长度最短最小
solution:
线段树 + 尺取法
首先很容易想到对于这个区间覆盖了,就是让区间中的每个点加1,那就是线段树,维护区间最大值
那么这时候就有一个问题:就是区间末尾可能很大。
这个时候就要用到离散化:
显然这道题只关心区间的长度,和区间之间的相互包含关系。因此可以预先把长度记录下来,把出现过的数按数值排序。 注意重复的数。。。
for(int i=1;i<=n;i++) { l[i]=read(),r[i]=read(); s[i].len=r[i]-l[i];s[i].num=i;//记录长度 ++cnt; b[cnt].num=i;b[cnt].val=l[i];b[cnt].bz=0; ++cnt; b[cnt].num=i;b[cnt].val=r[i];b[cnt].bz=1; } sort(b+1,b+cnt+1);//离散化 int q=0; for(int i=1;i<=cnt;i++) { if(b[i].val!=b[i-1].val) q++;//如果有重复得要 赋成一个值 if(b[i].bz==0) l[b[i].num]=q; else r[b[i].num]=q; } for(int i=1;i<=n;i++) { s[i].l=l[s[i].num]; s[i].r=r[s[i].num]; }//最后代回去
离散化完了,接下来怎么做? 这个时候如果当前的最大值小于了M,显然我们需要多加区间,而已经大于了,我们可以尝试减少区间来实现更有的解。 显然这和尺取法的思想一样,即选取区间有一定规律,或者说所选取的区间有一定的变化趋势的情况时,步步推进,放缩。。。
然后发现我们要维护最小长度,显然就按长度排序,再尺取法。。 减区间时线段树维护区间-1,加区间时维护区间+1,最后直接查询树顶。。。
先上代码
#include<bits/stdc++.h> using namespace std; const int MAXN = 500000 +10; const int inf = 1<<30; inline long long read() { int f=1,x=0; char ch; do { ch=getchar(); if(ch=='-') f=-1; }while(ch<'0'||ch>'9'); do { x=(x<<1)+(x<<3)+ch-'0'; ch=getchar(); }while(ch>='0'&&ch<='9'); return f*x; } inline int Max(int a,int b) { if(a>=b) return a; else return b; } int n,m; int l[MAXN],r[MAXN]; struct stick { int len; int l; int r; int num; friend bool operator < (stick a1,stick a2) { return a1.len<a2.len; } }s[MAXN*4]; struct node { int num; int val; bool bz; friend bool operator < (node a1,node a2) { return a1.val<a2.val; } }; int cnt =0; node b[MAXN*4]; struct stree { int l; int r; int maxv; inline int mid() { return (l+r)>>1; } }tree[MAXN * 8]; #define lc o<<1 #define rc o<<1|1 inline void build(int o,int l,int r) { tree[o].l=l,tree[o].r=r; if(l==r) tree[o].maxv=0; else { int mid=tree[o].mid(); build(lc,l,mid); build(rc,mid+1,r); tree[o].maxv=Max(tree[lc].maxv,tree[rc].maxv); return; } } int add[MAXN * 8]; inline void pushup(int o) { add[lc]+=add[o]; add[rc]+=add[o]; tree[lc].maxv+=add[o]; tree[rc].maxv+=add[o]; add[o]=0; } inline void update(int o,int x,int y,int w) { int l=tree[o].l,r=tree[o].r; if(r<x||l>y) return; if(x<=l&&y>=r) { add[o]+=w; tree[o].maxv+=w; return; } else { pushup(o); update(lc,x,y,w); update(rc,x,y,w); tree[o].maxv=Max(tree[lc].maxv,tree[rc].maxv); } } int ans=1<<30; int main() { n=read();m=read(); for(int i=1;i<=n;i++) { l[i]=read(),r[i]=read(); s[i].len=r[i]-l[i];s[i].num=i; ++cnt; b[cnt].num=i;b[cnt].val=l[i];b[cnt].bz=0; ++cnt; b[cnt].num=i;b[cnt].val=r[i];b[cnt].bz=1; } sort(b+1,b+cnt+1); int q=0; for(int i=1;i<=cnt;i++) { if(b[i].val!=b[i-1].val) q++; if(b[i].bz==0) l[b[i].num]=q; else r[b[i].num]=q; } for(int i=1;i<=n;i++) { s[i].l=l[s[i].num]; s[i].r=r[s[i].num]; } build(1,1,q); sort(s+1,s+n+1); int li=0,ri=0; while(1) { while(tree[1].maxv<m&&ri<=n) { ri++; update(1,s[ri].l,s[ri].r,1); } if(tree[1].maxv<m) break; while(tree[1].maxv>=m&&li<=ri) { li++; update(1,s[li].l,s[li].r,-1); } ans=min(ans,s[ri].len-s[li].len); } if(ans==inf) { printf("-1"); return 0; } printf("%d\n",ans); }
注意
1.第一次写的时候把建树的一个L写成了1... 然后全RE,然后非常智障的认为是数组开小了,然后非常智障的又交了几次。。。 2.第二个错是离散化后忘了这一步。。。
for(int i=1;i<=n;i++) { s[i].l=l[s[i].num]; s[i].r=r[s[i].num]; }
3.有一次在考试时做过这道题,当时很naive的写的纯模拟,因为根本没想到用线段树,然后模拟加树状数组拿了50分,其他人都拿了60分QwQ 模拟代码
// luogu-judger-enable-o2 #include<bits/stdc++.h> using namespace std; const int inf = 1<<30; int n,m; struct node { int l,r; inline friend bool operator < (node a,node b) { return a.l<b.l; } };node t[100000+ 10]; inline int read() { int f=1,x=0; char ch; do { ch=getchar(); if(ch=='-') f=-1; }while(ch<'0'||ch>'9'); do { x=(x<<3)+(x<<1)+ch-'0'; ch=getchar(); }while(ch>='0'&&ch<='9'); return f*x; } int minn,maxn; int ans=inf; int b[500010]; int c[500001]; int lb(int x){return x&-x;}; inline void change(int a,int b) { if(a) { while(a<=maxn) { c[a]+=b; a+=lb(a); } } } inline int g_sum(int x) { int re=0; while(x>=1) { re+=c[x]; x-=lb(x); } return re; } bool boo; int main() { n=read(); minn=inf; m=read(); for(int i=1;i<=n;i++) { t[i].l=read(),t[i].r=read(); minn=min(minn,t[i].l); maxn=max(maxn,t[i].r); change(t[i].l,1); change(t[i].r+1,-1); } sort(t+1,t+n+1); for(register int i=minn;i<=maxn;i++) { if(g_sum(i)>=m) { boo=true; int num=0; int maxx=0,minx=inf; for(register int j=1;j<=n;j++) { if(i>=t[j].l&&i<=t[j].r) { num++; // cout<<b[num]<<endl; b[num]=t[j].r-t[j].l; } if(t[j].l>i) break; } sort(b+1,b+num+1); int xiao = inf; for(register int i=1;i<=num-m+1;i++) { if(b[i+m-1]-b[i]<xiao) xiao=b[i+m-1]-b[i]; } ans=min(ans,xiao); /* for(int i=1;i<=num;i++) { cout<<b[i]<<" "<<endl; }*/ } } if(ans!=inf) cout<<ans<<endl; else cout<<-1<<endl; }