1. 程式人生 > 其它 >「2021 集訓隊互測」學姐買瓜

「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);
}