[CF1523G]Try Booking
[CF1523G]Try Booking
壹、题目描述 ¶
贰、题解 ¶
简直就是妙妙题......
有一个可以想到的是 —— 会不会相邻 \(x\) 的答案有通性?但是立马就被否决了,假设 \(x\) 变小的时候,相应的就会有一些区间合法,那么就会有一些区间因为前面的区间合法而变得不合法,然后又因为前面的不合法,后面又有一些区间合法......不难发现,\(x\) 的变化,带来的影响无穷尽也。
但对于一个 \(x\) 考虑的是长度大于等于 \(x\) 的区间,不难发现 \(x\) 的区间一定是 \(x+1\) 的区间的超集。但是这个东西有什么用呢?
考察一个 \(x\) 的区间集合中合法区间的数量,显然不超过 \(\lfloor{n\over x}\rfloor\),那么,对于每个 \(x\) 都找到合法区间的时间复杂度为:
\[{n\over 1}+{n\over 2}+{n\over 3}+\cdots+{n\over n}=n\ln n
\]
也就是说,只要我们不去找那些其他的区间,知道每次询问我们单刀直入,找到我们想要的区间,那么最后的复杂度只会是 \(\mathcal O(n\ln n\times K)\)(其中 \(K\) 为找到合法区间所用的时间)的复杂度!
考虑递归,对于区间 \([L,R]\),我们找到满足 \(L\le l\le r\le R\) 的区间中编号最小的区间,然后再递归 \([L,l-1],[r+1,R]\) 进行处理,而找到那个区间是一个动态二维偏序,考虑使用树套树。
所以最后复杂度为 \(\mathcal O(n\ln n\log^2n)\). 代码实现的时候可以将长度倒着来,这样就没有必要在线段树中删区间,而是可以无脑加区间了。
叁、参考代码 ¶
#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
using namespace std;
#define NDEBUG
#include<cassert>
namespace Elaina{
#define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
#define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
#define mmset(a, b) memset(a, b, sizeof a)
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
template<class T>inline T fab(T x){ return x<0? -x: x; }
template<class T>inline void getmin(T& x, const T rhs){ x=min(x, rhs); }
template<class T>inline void getmax(T& x, const T rhs){ x=max(x, rhs); }
template<class T>inline T readin(T x){
x=0; int f=0; char c;
while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
return f? -x: x;
}
template<class T>inline void writc(T x, char s='\n'){
static int fwri_sta[1005], fwri_ed=0;
if(x<0) putchar('-'), x=-x;
do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
putchar(s);
}
}
using namespace Elaina;
const int maxn=5e4;
const int maxm=1e5;
const int inf=0x3f3f3f3f;
struct interval{
int l, r, id;
interval(){}
interval(int L, int R, int I): l(L), r(R), id(I){}
inline int operator <(const interval rhs) const{
return id<rhs.id;
}
}a[maxm+5];
vector<interval>v[maxn+5];
int ans[maxn+5], n, m;
inline void input(){
n=readin(1), m=readin(1);
int l, r;
rep(i, 1, m){
l=readin(1), r=readin(1);
a[i]=interval(l, r, i);
v[r-l+1].push_back(a[i]);
}
}
namespace sgtre{
struct node{
int ls, rs, mn;
node(){ ls=rs=0, mn=inf; }
}tre[maxn*250+5];
int ncnt=0;
#define ls tre[i].ls
#define rs tre[i].rs
#define mid ((l+r)>>1)
inline int newnode(){
assert(ncnt<=maxn*250);
return ++ncnt;
}
void modify(int& i, int l, int r, int p, int v){
if(!i) i=newnode();
getmin(tre[i].mn, v);
if(l==r) return;
if(p<=mid) modify(ls, l, mid, p, v);
else modify(rs, mid+1, r, p, v);
}
int query(int i, int l, int r, int L, int R){
if(!i) return inf;
if(L<=l && r<=R) return tre[i].mn;
int ret=inf;
if(L<=mid) ret=query(ls, l, mid, L, R);
if(mid<R) getmin(ret, query(rs, mid+1, r, L, R));
return ret;
}
#undef mid
#undef ls
#undef rs
}
namespace saya{
int rt[maxn<<1|1];
#define mid ((l+r)>>1)
#define __i (((l)+(r))|((l)!=(r)))
void modify(int l, int r, int L, int R, int v){
sgtre::modify(rt[__i], 1, n, R, v);
if(l==r) return;
if(L<=mid) modify(l, mid, L, R, v);
else modify(mid+1, r, L, R, v);
}
int query(int l, int r, int L, int R){
if(L<=l && r<=R) return sgtre::query(rt[__i], 1, n, L, R);
int ret=inf;
if(L<=mid) getmin(ret, query(l, mid, L, R));
if(mid<R) getmin(ret, query(mid+1, r, L, R));
return ret;
}
#undef mid
#undef __i
}
int res;
void solve(int l, int r){
if(l>r) return;
int ret=saya::query(1, n, l, r);
if(ret>m) return; // it's not @p n but @p m
res+=a[ret].r-a[ret].l+1;
solve(l, a[ret].l-1); solve(a[ret].r+1, r);
}
signed main(){
input();
drep(i, n, 1){
for(interval cur: v[i])
saya::modify(1, n, cur.l, cur.r, cur.id);
res=0; solve(1, n);
ans[i]=res;
}
rep(i, 1, n) writc(ans[i]);
return 0;
}
肆、关键的地方 ¶
合法区间的数量不超过 \(n\ln n\) && 对于区间覆盖问题的区间递归处理方法。