「2021 集训队互测」学姐买瓜

考虑暴力

容易写出一下代码

void solve(int L, int R) {
    len = 0;

    for (int i = 1 ; i <= tot ; i++)
        if (L <= t[i].L && t[i].R <= R)len++, q[len] = t[i];
    
    int ans = 0, last = 0;
    sort(q + 1, q + 1 + len, cmp);

    for (int i = 1 ; i <= len ; i++)if (last <= q[i].L)last = q[i].R + 1, ans++;

    cout << ans << endl;
}

提炼一下上面暴力的主要思想:

从左往右,对于一个线段能放就放(满足 已经放置的 最右 线段端点 <= 当前要放置的 线段 的左端点)


考虑快速处理上述 过程--------------对于每一个 在原序列里面的线段\(S\) 维护一个后缀 区间 \(P\)

满足 \(R_S < L_p , 且R_p最小\)

暴力的想法: 每插入一个线段 就对 所有的 线段 看看 这个后继是否更新

直接做直接寄

考虑优化这个过程

需要支持 :

区间修改后继

快速跳后继

于是就可以想到分块

考虑对原序列分成\(B\)

块内: 维护块内 每一个块 往右跳线段 跳出这个块 所到达的 最靠左 的块,即相应的点

​ 每一个位置 跳一次线段后 不跳出块内 所能 到达的 最靠左的位置

块外: 维护一个整体 修改这个后继的标记 (即整体取min)

于是就可以写写代码了(借鉴了某位老哥的代码吧)

fa[i] 在i这个块内跳一次所到达的 位置

to[i] 以i这个位置为起点,跳出这个块 所能到达的最左位置

tag[i] 区间取min

v[i]: 相应的跳跃次数

inline void ins(int L , int R){
	for(int i = 1 ; i < rk[L] ; i++)tag[i] = min(tag[i] , R + 1);
	if(tag[rk[L]] != INF){
		for(int i = RR[rk[L]] ; i >= LL[rk[L]] ; i--){
			if(tag[rk[L]] < fa[i]){
				fa[i] = to[i] = tag[rk[L]] , v[i] = 1;
			}
		}
		tag[rk[L]] = INF;
	}
	if(rk[L] == rk[R + 1]){
		for(int i = L ; i >= LL[rk[L]] ; i--){
			if(R + 1 < fa[i]){
				fa[i] = R + 1;
				v[i] = v[R + 1] + 1;
			}
		}
	}
	else{
		for(int i = L ; i >= LL[rk[L]] ; i--){
			if(R + 1 < fa[i]){
				fa[i] = to[i] = R + 1;
				v[i] = 1;
			}
		}
	}
	for(int i = RR[rk[L]] ; i >= LL[rk[L]] ; i--){
		if(fa[i] <= RR[rk[L]]){
			v[i] = v[fa[i]] + 1;
			to[i] = to[fa[i]];
		}
	}
}


inline void solve(int L , int R){
	int now = L , zz = 0;
	int minl = INF , minl2 = INF , V;
	while(now <= R){
		minl = min(tag[rk[now]] , fa[now]);
		minl2 = min(tag[rk[now]] , to[now]);
		V = v[now];
		if(minl > R + 1)break;
		if(minl2 <= R + 1)zz += V , now = minl2;
		else zz++ , now = minl;
	}
	printf("%d\n" , zz);
}
posted @ 2022-03-24 20:44  After_rain  阅读(252)  评论(0)    收藏  举报