1. 程式人生 > 其它 >【題解】[JOISC 2021 Day4] イベント巡り 2

【題解】[JOISC 2021 Day4] イベント巡り 2

求字典序最小的答案,從前往後一次考慮每一位。

即列舉 \(i\)\(1\)\(n\) 判斷當前活動加入後是否能夠達到 \(k\) 的總數,如果能則加入當前的 \(i\)

這樣我們只用線上維護當前空缺位置能夠加入活動的最大值。

考慮加入活動 \(i\) 後,最多由原來的一個連續區間斷開成兩個連續的區間,所以我們只用快速計算區間 \([l,r]\) 中最多能放入的方案數。

由於是靜態問題,考慮倍增。令 $f[i][j] $ 表示從位置 \(i\) 開始,加入 \(j\) 個區間後到達的最近位置。

初值 \(f[L_i][i]=\min\{R_i\}\)

轉移 \(f[i][0]=\min\{f[i][0],f[i+1][0]\}\)

\(f[i][j]=\min\{f[i+1][j],f[f[i][j-1][j-1]]\}\)

對於空缺的位置,我們過載運算子 $< $ ,\([l_1,r_1]<[l_2,r_2]\) 當且僅當 \(r_1<l_2\) ,然後用 set 維護即可。

時間複雜度 \(\mathcal{O}(n\log n)\)

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 200005
using namespace std;
int n,k,L[N],R[N],f[N][17],t,o[N],b[N],T,w;
int calc(int l,int r){
	if(l>r)return 0;int now=0;
	pre(i,t,0)if(f[l][i]<=r+1)now+=1<<i,l=f[l][i];
	return now;
}
struct node{
	int l,r;
	node(int ll=0,int rr=0){l=ll,r=rr;}
	bool operator<(const node o)const{return r<o.l;}
};set<node>s;
int main(){
	scanf("%d%d",&n,&k);t=log2(k);
	rep(i,1,n)scanf("%d%d",&L[i],&R[i]),o[++w]=L[i],o[++w]=R[i];
	sort(o+1,o+w+1);rep(i,1,w)if(o[i]!=o[i-1])b[++T]=o[i];
	rep(i,1,n)L[i]=lower_bound(b+1,b+T+1,L[i])-b,R[i]=lower_bound(b+1,b+T+1,R[i])-b;
	rep(i,1,T+2)rep(j,0,t)f[i][j]=T+2;
	rep(i,1,n)f[L[i]][0]=min(f[L[i]][0],R[i]);
	pre(i,T,1){
		f[i][0]=min(f[i][0],f[i+1][0]);
		rep(j,1,t)f[i][j]=min(f[i+1][j],f[f[i][j-1]][j-1]);
	}puts("No Copy");
	s.insert(node(1,T));int sum=calc(1,T);
	if(sum<k){printf("-1\n");return 0;}
	rep(i,1,n){
		if(s.find(node(L[i],R[i]-1))==s.end())continue;
		node cur=*s.find(node(L[i],R[i]-1));
		if(cur.l<=L[i]&&cur.r>=R[i]-1){
			int dta=calc(cur.l,L[i]-1)+calc(R[i],cur.r)-calc(cur.l,cur.r);
			if(dta+sum>=k-1){
				printf("%d\n",i);k--;
				sum+=dta;s.erase(cur);
				if(cur.l<L[i])s.insert(node(cur.l,L[i]-1));
				if(cur.r>=R[i])s.insert(node(R[i],cur.r));
			}
		}
		if(!k)return 0;
	}
	return 0;
}