JZM 的国赛路 Ⅱ(倍增+数据结构)

题面

JZM 终于通过了那条单行道

现在他正要通过加油站。在他面前是一条极宽的马路,当然马路的长度更是远长于宽,现在从 JZM 的位置算起,往前延申就有 1,000,000 km 左右的长度。马路上有 N 条(条?)加油站,每条加油站从距离 JZM L i L_i Li 米处延伸到距离 JZM R i R_i Ri 米处。JZM 可以从一条加油站的开头 ( L i L_i Li 处) 进入,然后从结尾 ( R i R_i Ri 处) 出加油站,然后可以横向走,到其它加油站的开头,但是不能逆行,也就是说,他的下一个加油站的 L L L 必须大于等于上一个加油站的 R R R

JZM 必须通过 K 条加油站,才能加满油,因此,他想知道一种通过加油站的方案。如果有多个通过加油站的方案,JZM 想知道字典序最小的加油方案。具体地说,如果有两种方案,把通过的加油站编号从小到大排序后形成的数组分别为 A 和 B(长度当然都为 K),那么 A 和 B 中字典序更小的数组代表的方案将优先考虑。由于加油站对电子设备有些限制,所以 JZM 把这个问题抛给了你。

注意:由于马路很宽,因此不同的加油站可以并排存在,也就是说它们的可能存在 L i ≤ L j ≤ R i ≤ R j L_i\leq L_j\leq R_i\leq R_j LiLjRiRj 或者 L i ≤ L j < R j ≤ R i L_i\leq L_j <R_j\leq R_i LiLj<RjRi 的情况。

  • 1 ≤ N ≤ 100000. 1 ≤ N ≤ 100 000. 1N100000.
  • 1 ≤ K ≤ N . 1 ≤ K ≤ N. 1KN.
  • 1 ≤ L i < R i ≤ 1 , 000 , 000 , 000    ( 1 ≤ i ≤ N ) . 1 ≤ L_i < R_i ≤ 1,000,000,000\;(1 ≤ i ≤ N). 1Li<Ri1,000,000,000(1iN).

输入

N      K N\;\;K NK
L i      R i L_i\;\;R_i LiRi
. . . ... ...
L N      R N L_N\;\;R_N LNRN

输出

K 行,第 i 行为你的字典序最小的方案中从小到大第 i 个编号。若无法通过不小于 K 条加油站,则输出 -1.

样例

Sample Input 1

5 4
1 3
2 5
8 9
6 8
10 15

Sample Output 1

1
3
4
5

Sample Input 2

20 16
250732298 258217736
26470443 34965880
252620676 260043105
692063405 697656580
497457675 504191511
391372149 397942668
858168758 867389085
235756850 241022021
585764751 593366541
207824318 217052204
661682908 671226688
886273261 892279963
770109416 778960597
264372562 270395107
176883483 186662376
509929119 519063796
109491630 118520141
162731982 168101507
662727316 668317158
757072772 765493222

Sample Output 2

1
2
4
5
6
7
8
9
10
11
12
13
14
15
16
17

题解

我们可以找到一种宏观的思路:编号从 1 开始枚举加油站,如果选了该加油站后整个方案的最大加油站数量仍然不小于 K ,就一定把该加油站选上,然后再考虑下一个加油站。

这个过程可以这样维护:新加入一个加油站有个前提,即这个加油站不会跟先前已选的加油站交叉或包含,这个随便用个树状数组维护就行了。新加入一个加油站后会把一段区间切成两半,这两半是互不干涉的了,因此我们把原先这段区间的答案减去,再加上切半后的左半边区间答案+右半边区间答案+1就是选了这个区间后最大加油站数量。

因此这道题的核心就是快速求任意一段区间内(隔绝)单独考虑方案的最大不相交加油站数量。

N 2 \rm N^2 N2

我们把加油站按照右端点放到数轴上(已离散化),于是每次求一个区间 [ l , r ] [l,r] [l,r] 的答案时,就可以从左端点开始做一个 DP。 d p [ i ] dp[i] dp[i] 表示右端点小于等于 i i i 的最大不相交数量,那么:

d p [ i ] = max ⁡ ( d p [ i − 1 ] , max ⁡ R j = i , L j ≥ l d p [ L j ] + 1 ) dp[i]=\max(dp[i-1],\max_{R_j=i,L_j\geq l}dp[L_j]+1) dp[i]=max(dp[i1],Rj=i,Ljlmaxdp[Lj]+1)

这样一来算一个区间就是 O ( N ) O(N) O(N) 的了,总复杂度 O ( N 2 ) O(N^2) O(N2)

N N \rm N\sqrt N NN

我们可以反着考虑,某个点开始往前通过 X 个加油站后的最近距离。如果我们提前把每个点往前通过 1 1 1~ N {\sqrt N} N 个加油站的最近距离都处理出来,然后算区间 [ l , r ] [l,r] [l,r] 时就直接枚举,这不就能做到 O ( N N ) O(N\sqrt N) O(NN ) 了吗?

N log ⁡ N \rm N\log N NlogN

其实根本没必要想根号的做法,我们直接存每个点开始往前通过 2 x 2^x 2x 个加油站后的最近距离,然后算区间 [ l , r ] [l,r] [l,r] 时直接倍增不就完了?

CODE

#include<set>
#include<map>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 100005
#define DB double
#define LL long long
#define ENDL putchar('\n')
#define lowbit(x) (-(x) & (x))
#define eps 1e-9
#define FR first
#define SE second
#define SI set<pair<int,int> >::iterator 
#define makepair(x,y) (pair<int,int>){(x),(y)}
LL read() {
	LL f = 1,x = 0;char s = getchar();
	while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
	while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
	return f * x;
}
const int MOD = 1000000007;
int n,m,i,j,s,o,k;
int ll[MAXN],rr[MAXN];
int b[MAXN<<1],cnb,NM;
vector<int> pr[MAXN<<1],pl[MAXN<<1];
map<int,int> mp;
int dp[MAXN<<1];
bool cmpid(int a,int b) {return dp[a] > dp[b];}
int rmq[MAXN<<1][21];// 18
int cont(int l,int r) {
	int as = 0,ad = r;
	for(int i = 18;i >= 0;i --) {
		if(rmq[ad][i] >= l) {
			as += (1<<i);
			ad = rmq[ad][i];
		}
	}
	return as;
}
int c[MAXN<<1];
void addt(int x,int y) {while(x<=NM)c[x]+=y,x+=lowbit(x);}
int sum(int x) {int as=0;while(x>0)as+=c[x],x-=lowbit(x);return as;}
set<pair<int,int> > st;
int as[MAXN];
int c2[MAXN];
int id[MAXN];
void addc(int x,int y) {while(x>0)c2[x] = min(c2[x],y),x -= lowbit(x);}
int minc(int x) {
	int as = 0x7f7f7f7f;
	while(x<=NM)as = min(as,c2[x]),x += lowbit(x);
	return as;
}
int main() {
	n = read();k = read();
	for(int i = 1;i <= n;i ++) {
		s = ll[i] = read();
		o = rr[i] = read();
		b[++ cnb] = s;
		b[++ cnb] = o;
	}
	sort(b + 1,b + 1 + cnb);
	for(int i = 1;i <= cnb;i ++) {
		if(i==1 || b[i] > b[i-1]) {
			mp[b[i]] = ++ NM;
		}
	}
	for(int i = 1;i <= n;i ++) {
		ll[i] = mp[ll[i]];rr[i] = mp[rr[i]];
		pr[rr[i]].push_back(i);
		pl[ll[i]].push_back(i);
	}
	for(int j = 0;j <= 18;j ++) rmq[0][j] = -1;
	for(int i = 1;i <= NM;i ++) {
		for(int j = 0;j <= 18;j ++) rmq[i][j] = -1;
		for(int j = 0;j < (int)pr[i].size();j ++) {
			int y = pr[i][j];
			rmq[i][0] = max(rmq[i][0],ll[y]);
		}
		rmq[i][0] = max(rmq[i][0],rmq[i-1][0]);
		for(int j = 1;j <= 18;j ++) {
			if(rmq[i][j-1] < 0) break;
			rmq[i][j] = rmq[rmq[i][j-1]][j-1];
		}
	}
	int sm = 0;
	st.insert(makepair(1,sm = cont(1,NM)));
	st.insert(makepair(NM,0));
	int cn = 0;
	for(int i = 1;i <= n;i ++) {
		if(cn >= k) break;
		if(!(sum(rr[i])-sum(ll[i]))) {
			SI t = st.upper_bound(makepair(ll[i],0x7f7f7f7f));
			int ri = t->FR;
			t --;
			int sm2 = sm - t->SE,li = t->FR;
			int cl = cont(li,ll[i]),cr = cont(rr[i],ri);
			if(sm2 + cl + cr + 1 >= k) {
				st.erase(*t);
				st.insert(makepair(li,cl));
				st.insert(makepair(ll[i],1));
				st.insert(makepair(rr[i],cr));
				sm = sm2+cl+cr+1;
				as[++ cn] = i;
				for(int j = ll[i]+1;j <= rr[i];j ++) addt(j,1);
			}
		}
	}
	if(cn < k) printf("-1\n");
	else for(int i = 1;i <= k;i ++) {
		printf("%d\n",as[i]);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值