LG3626 [APIO2009]会议中心
前言
倍增的好题。不得不说,APIO 的题质量很高。
解法
为了方便计算,我们可以进行对每个节点离散化,最多会有 (2n) 个节点。
首先假设我们已经求得了最大值。我们需要来构造一组方案。
首先可以确定,我们要从第一条线段开始考虑,因为这样字典序一定最小。
我们令 (g_{i,j}) 为从点 (i) 开始,到 (j) 为止最多有多少线段。(注意,不一定要以 (i) 为起点,也不一定以 (j) 为终点)
如果考虑一个待选区间 ([l,r]) 是否可以选择。如果现在把被选掉的点填上,对于目前最大的满足 (i<l,r<j),且 ([i,j]) 中没有一个日期被安排掉的 ([i,j]),若满足 (g_{i,l-1}+g_{r+1,j}+1=g_{i,j}),则这个区间可以被加入答案。读者自证不难
这样既保证了答案最大,也保证了这条线段可以被加入。确定 ([i,j]) 可以用树状数组来实现,看代码就可以理解了。不放心还可以用线段树来做,思路会很清晰,只是代码烦了亿些。
问题是,我们如何快速求出 (g(i,j)) 呢?
可以考虑倍增。
我们令 (f_{i,j}) 为从点 (i) 开始往后跳 (2^j) 条线段所能到达的最小的右端点的值。同样,起点不一定是 (i)。
转移方程很简单(为了表示清楚,就用数组的形式了,下标表示有点乱):
[f[i][j]=f[f[i][j-1]+1][j-1]
]
初值是一个难点,我们可以先用每个区间更新一波。我们再做一遍 (f_{i,0}=min(f_{i,0},f_{i+1,0}))。想到这个不容易,理解很简单。
需要铲平一个误区,对于不是区间起点的点,也需要做上述倍增,这样方便其它点的转移与维护。
我们可以在 (O(log n)) 时间内求出 (g_{i,j}) 了。具体怎么做,看代码你就秒懂了。
代码
如果你想写线段树,那我祝你好运了。
//Don't act like a loser.
//This code is written by huayucaiji
//You can only use the code for studying or finding mistakes
//Or,you'll be punished by Sakyamuni!!!
#include<bits/stdc++.h>
#define int long long
#define lowbit(x) x&(-x)
using namespace std;
int read() {
char ch=getchar();
int f=1,x=0;
while(ch<'0'||ch>'9') {
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') {
x=x*10+ch-'0';
ch=getchar();
}
return f*x;
}
const int MAXN=4e5+10;
int n,m,cnt;
int number[MAXN],f[MAXN][23],ans[MAXN];
struct line {
int l,r;
}e[MAXN];
int uq(int x) {
return lower_bound(number+1,number+m+1,x)-number;
//离散化
}
int findmax(int l,int r) {
int ans=0;
for(int i=20;i>=0;i--) {
if(f[l][i]<=r) {
ans+=(1<<i);
l=f[l][i]+1;
}
}
return ans;
}
struct bitmax {
int c[MAXN];
void add(int x,int val) {
for(;x<=m;x+=lowbit(x)) {
c[x]=max(c[x],val);
}
}
int query(int x) {
int ret=0;
for(;x;x-=lowbit(x)) {
ret=max(ret,c[x]);
}
return ret;
}
}lm,rm;
struct bitsum {
int c[MAXN];
void add(int x) {
for(;x<=m;x+=lowbit(x)) {
c[x]++;
}
}
int query(int x) {
int ret=0;
for(;x;x-=lowbit(x)) {
ret+=c[x];
}
return ret;
}
}ls,rs;
signed main() {
cin>>n;
for(int i=1;i<=n;i++) {
e[i].l=read();
e[i].r=read();
number[++m]=e[i].l;
number[++m]=e[i].r;
}
sort(number+1,number+m+1);
m=unique(number+1,number+m+1)-number-1;
for(int i=1;i<=m+1;i++) {
f[i][0]=m+1;
}
for(int i=1;i<=n;i++) {
e[i].l=uq(e[i].l);
e[i].r=uq(e[i].r);
f[e[i].l][0]=min(f[e[i].l][0],e[i].r);
}
for(int i=m;i;i--) {
f[i][0]=min(f[i][0],f[1+i][0]);
}
for(int j=1;j<=20;j++) {
for(int i=1;i<=m+1;i++) {
if(f[i][j-1]<=m)
f[i][j]=f[f[i][j-1]+1][j-1];
else {
f[i][j]=m+1;
}
}
}
for(int i=1;i<=n;i++) {
if(cnt-rs.query(e[i].l-1)-ls.query(m-e[i].r)>0) {
continue;
}
//此时这个区间已经被覆盖了,无需考虑了。
int l=rm.query(e[i].l-1);
int r=m-lm.query(m-e[i].r);
if(findmax(l,e[i].l-1)+findmax(e[i].r+1,r)==findmax(l,r)-1) {
ans[++cnt]=i;
rs.add(e[i].r);
ls.add(m-e[i].l);
rm.add(e[i].r,e[i].r);
lm.add(m-e[i].l,m-e[i].l);
}
}
cout<<cnt<<endl;
for(int i=1;i<=cnt;i++) {
printf("%lld ",ans[i]);
}
puts("");
return 0;
}
/*
4
1 4
2 3
4 5
10 12
*/