房租 解题报告
房租 解题报告
题目描述
现在你有一个 \([1,n]\) 的区间,现在你将面对 \(m\) 个询问,每个询问都形如 \((l_i,r_i)\) ,你需要按照时间顺序依次处理这些询问。
所有询问都会提前给出。对于询问 \((l_i,r_i)\),它可以被接受当且仅当:
-
\(r_i-l_i+1 \ge x\)
-
已经被接受的区间任意一个区间与 \([l_i,r_i]\) 无交。
现在对于满足 \(x \in [1,n]\) 的任意一个 \(x\),求上述过程后,被接受区间的并的长度。
数据规模:\(n\le 5 \times 10^4\),\(m \le 1 \times 10^5\)
分析
首先我们发现,可被接受的区间个数为(调和基数):
\[\sum_{x=1}^n{\frac{n}{i}} \approx n\ln n
\]
那么我们只需要考虑怎么快速找出这些点就好了。我想不到,但出题人想到了分治。
对于区间 \([l,r]\) ,我们可以找到第一个满足条件的区间 \([x,y]\),然后分治区间 \([l,x-1]\) 和 \([y+1,r]\)。
什么?你问我怎么找第一个满足条件的区间。条件不就是:
-
\(l \le x\)
-
\(y \le r\)
-
\([x,y]\) 的时间最小
这不就二维偏序。树套树维护一下就行。
启示
- 没有想法就分治
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define Inf (1ll<<60)
#define For(i,s,t) for(int i=s;i<=t;i++)
#define Down(i,s,t) for(int i=s;i>=t;i--)
//#define ls (i<<1)
//#define rs (i<<1|1)
#define lowbit(x) ((x)&(-(x)))
using namespace std;
typedef long long ll;
inline int min(int x,int y){return x<y?x:y;}
inline int max(int x,int y){return x>y?x:y;}
inline ll min(ll x,ll y){return x<y?x:y;}
inline ll max(ll x,ll y){return x>y?x:y;}
inline int read(){
register int x=0,f=1;
char c=getchar();
while(c<'0' || '9'<c) f=(c=='-')?-1:1,c=getchar();
while('0'<=c && c<='9') x=(x<<1)+(x<<3)+c-'0',c=getchar();
return x*f;
}
void write(int x){
if(x>=10) write(x/10);
putchar(x%10+'0');
}
const int N=5e4+1000,LG=17*17,M=N*LG;
int n,m,ans[N],rk[N<<1];
struct Line{int l,r,id;}a[N<<1];
bool cmp(Line x,Line y){return x.r-x.l>y.r-y.l;}
int rt[N<<2],ls[M],rs[M],val[M],tot;
void build(int &i,int l,int r){
i=++tot;
if(l==r) return;
int mid=l+r>>1;
build(ls[i],l,mid);
build(rs[i],mid+1,r);
}
//内层修改 把x位置赋值k
void update0(int &i,int l,int r,int x,int k){
if(!i) i=++tot,val[i]=inf;//一定要赋初值
val[i]=min(val[i],k);
if(l==r) return;
int mid=l+r>>1;
if(x<=mid)
update0(ls[i],l,mid,x,k);
else
update0(rs[i],mid+1,r,x,k);
}
//内层询问 [x,y]的最小权值
int query0(int i,int l,int r,int x,int y){
if(!i) return inf;
if(x<=l && r<=y) return val[i];
int mid=l+r>>1;
if(x>mid) return query0(rs[i],mid+1,r,x,y);
if(mid>=y) return query0(ls[i],l,mid,x,y);
return min(query0(ls[i],l,mid,x,y),query0(rs[i],mid+1,r,x,y));
}
//外层修改 把x位置线段树上的y位置赋值k
void update(int i,int l,int r,int x,int y,int k){
update0(rt[i],1,n,y,k);
if(l==r) return;
int mid=l+r>>1;
if(x<=mid)
update(ls[i],l,mid,x,y,k);
else
update(rs[i],mid+1,r,x,y,k);
}
//外层询问 询问外层[x,n]区间的线段树上[1,y]区间的最小值
int query(int i,int l,int r,int x,int y){
if(!i) return inf;
if(x<=l && r<=n)
return query0(rt[i],1,n,1,y);
int mid=l+r>>1;
if(x>mid) return query(rs[i],mid+1,r,x,y);
if(mid>=n) return query(ls[i],l,mid,x,y);
return min(query(ls[i],l,mid,x,y),query(rs[i],mid+1,r,x,y));
}
int slove(int l,int r){
if(l>r) return 0;
int res_id=query(1,1,n,l,r);
if(res_id==inf) return 0;
res_id=rk[res_id];
return slove(l,a[res_id].l-1)+slove(a[res_id].r+1,r)+a[res_id].r-a[res_id].l+1;
}
int main()
{
freopen("rent.in","r",stdin);
freopen("rent.out","w",stdout);
n=read(),m=read();
For(i,1,m) a[i].l=read(),a[i].r=read(),a[i].id=i;
sort(a+1,a+m+1,cmp);
For(i,1,m) rk[a[i].id]=i;
int index=1;
build(rt[0],1,n);
Down(i,n,1){
while(index<=m && a[index].r-a[index].l+1>=i) update(1,1,n,a[index].l,a[index].r,a[index].id),index++;
ans[i]=slove(1,n);
}
For(i,1,n)
printf("%d\n",ans[i]);
//cout<<tot;
return 0;
}