LG3626 [APIO2009]会议中心(倍增+树状数组)

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
*/
posted @ 2021-04-30 20:09  huayucaiji  阅读(50)  评论(0编辑  收藏  举报