P11820 [PA 2015] 健身房 / Siłownia

P11820 [PA 2015] 健身房 / Siłownia

也许是另一种贪心做法,但是似乎需要卡空间,所以输麻了。

题意

\(m\) 个器材,有 \(n\) 个人要健身。

\(i\) 个人要在 \([l_i,r_i]\)其中一天使用器材 \(p_i\)

构造每个人健身的时刻,使得健身房有人的天数最少。

\(n \le 10^6, m \le 10^9\)

思路

首先器材可以离散化,变成 \(m \le 10^6\)

首先想想有没有什么贪心做法。

先假设有解。

按照 \(l\) 扫描或者按照 \(r\) 扫描,升序或者降序,都试一下能不能贪心。

发现可以按照 \(r\) 升序排序。对于第 \(i\) 个人:

  1. 如果目前健身房在 \([l_i,r_i]\) 都没有人,或者有人的时刻器材 \(p_i\) 已经被占用,那么这个人应该尽量在接近右端点的时刻,新开一天,去健身。因为这样后面的人更有机会和他在同一天健身。
  2. 如果健身房目前在 \([l_i,r_i]\) 的其中几天有人,那么这个人尽量在接近左端点的,且那一天 \(p_i\) 有空的时刻健身。因为这样可以留出更靠后的时刻给同样使用 \(p_i\) 的后人。

对于情况 \(1\),如果 \(r_i\)\(p_i\) 已经被占用,那么就要考虑第 \(r_i-1\) 天。不过这个应该是可以处理的。

但是假如 \([l_i,r_i]\) 期间 \(p_i\) 都被占用了,我们需要让其中一天占用 \(p_i\) 的人换到 \(<l_i\) 的某一天健身。这样贪心就需要反悔,很烦。


这一段感觉有教育意义。

我们有一种方法可以保证情况 \(1\) 直接选择 \(r_i\) 合法。

现在的问题是,可能有一些人选择的时刻太后了,导致后人没有时刻可以选择。

比如我们有 \(3\) 个要用同样器材的人:\([1,3],[3,4],[3,4]\)

这时第一个人就只能选择 \([1,2]\),不能选择 \(3\)。而且他在 \([1,2]\) 的任意一天都可以。

于是我们更新一下每个人的右端点。具体地:

按照 \(l\) 降序排序,对于第 \(i\) 个人,他目前能选择的最后一个有空的时刻是 \(x\),那么将他的 \(r_i'\) 设成 \(x\)

这样可以保证,只要第 \(i\) 个人在 \(r_i'\) 及之前健身,他就不会祸害后人。

这里可以顺便判出是否有解。


更新 \(r_i\) 时,如何找到最后一个有空的时刻?

可以使用 set 维护,每个器材开一个 set,维护所有没有被占用的时刻(以区间形式维护)。所有 set 的区间个数之和是 \(O(n)\) 的。

你也可以对每个器材开一个动态开点线段树,维护这个器材被占用的时刻。然后线段树二分找到 \(\le r_i\) 的最大的未被占用时刻。


按照 \(r\) 升序排序时,怎么在找到健身房有人且器材 \(p\) 有空的 \([l,r]\) 内最早的时刻?

你仍然可以使用 set 维护健身房有人的时刻,和每个器材有空的时刻。

但是怎么找同时满足健身房有人,器材有空的时刻?

这个我真的想不到怎么用 set 这一类线性空间的东西做。大蛇看到能不能教教/kel

我们可以使用动态开点线段树,维护健身房有人的时刻,和每个器材有人的时刻。

然后线段树二分,具体地,当前线段树节点是 \([l,r]\),如果 \([l,mid]\) 满足健身房有人且器材有空的时刻数量(即健身房有人减去该器材有人的时刻数量)\(>0\),那么就递归进 \([l,mid]\)


时间复杂度 \(O(n \log V)\)

但是动态开点线段树的空间是 \(O(n \log V)\) 的。提供一些卡空间方法:

  1. 结构体注意地址对齐什么的问题。
  2. 一个指针的空间在 64 位机子上与 long long 相同,所以不能用指针写动态开点线段树了/ll。
  3. 线段树节点不要存 \(l,r\)
  4. 更新完所有 \(r_i\) 之后,只有所有的时刻 \(r_i\) 是有用的,有用时刻只有 \(n\) 个。
  5. 注意到该题时限 \(8s\),你可以适当地用时间换空间。

code

这个贪心套路应该要学会。

这个题的数据好像还蛮强的。

最大点空间 510MB。

空间应该能再卡卡。虽然卡得意犹未尽,但是都过了为什么还要卡。

#include<bits/stdc++.h>
#define sf scanf
#define pf printf
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
using namespace std;
typedef long long ll;
namespace wing_heart {
	constexpr int N=1e6+7,lim=1e9;
	int n,m,lx;
	struct piii {
		int id,l,r,p;
	}a[N];
	int anss[N];
	bool cmpl (piii a,piii b) { return a.l < b.l; }
	bool cmpr (piii a,piii b) { return a.r < b.r; }
	int ap[N],ar[N]; 
	struct node {
		int s;
		int ls,rs;
		void newnode(int _s=0) { s=_s, ls=rs=0; }
	}tr[N*50];
	int cnt;
	int rt[N];
	void pushup(int u) {
		tr[u].s=tr[tr[u].ls].s + tr[tr[u].rs].s; 
	}
	int insert(int u,int l,int r,int L,int R) {
		if(r<L || !tr[u].s) return 0;
		if(l==r) {
			tr[u].s=0;
			return l;
		}
		int mid=(l+r)>>1;
		if(tr[u].ls==0) tr[u].ls = cnt++, tr[tr[u].ls].newnode(mid-l+1);
		if(tr[u].rs==0) tr[u].rs = cnt++, tr[tr[u].rs].newnode(r-mid);
		int s=0;
		if(R<=mid) s=insert(tr[u].ls,l,mid,L,R);
		else {
			s=insert(tr[u].rs,mid+1,r,L,R);
			if(!s) s=insert(tr[u].ls,l,mid,L,R);
		}
		pushup(u);
		return s;
	}
	int insert2(int u1,int u2,int l,int r,int L,int R) {
		if(l>R || !(tr[u1].s-(r-l+1-tr[u2].s))) return 0;
		if(l==r) {
			tr[u2].s=0;
			return l;
		}
		int mid=(l+r)>>1;
		if(tr[u1].ls==0) tr[u1].ls = cnt++, tr[tr[u1].ls].newnode(0);
		if(tr[u1].rs==0) tr[u1].rs = cnt++, tr[tr[u1].rs].newnode(0);
		if(tr[u2].ls==0) tr[u2].ls = cnt++, tr[tr[u2].ls].newnode(mid-l+1);
		if(tr[u2].rs==0) tr[u2].rs = cnt++, tr[tr[u2].rs].newnode(r-mid);
		int s=0;
		if(L>mid) s=insert2(tr[u1].rs,tr[u2].rs,mid+1,r,L,R);
		else {
			s=insert2(tr[u1].ls,tr[u2].ls,l,mid,L,R);
			if(!s) s=insert2(tr[u1].rs,tr[u2].rs,mid+1,r,L,R);
		}
		pushup(u2);
		return s;
	}
	void change(int u1,int u2,int l,int r,int x) {
		if(l==r) {
			tr[u1].s=1; tr[u2].s=0;
			return;
		}
		int mid=(l+r)>>1;
		if(tr[u1].ls==0) tr[u1].ls = cnt++, tr[tr[u1].ls].newnode(0);
		if(tr[u1].rs==0) tr[u1].rs = cnt++, tr[tr[u1].rs].newnode(0);
		if(tr[u2].ls==0) tr[u2].ls = cnt++, tr[tr[u2].ls].newnode(mid-l+1);
		if(tr[u2].rs==0) tr[u2].rs = cnt++, tr[tr[u2].rs].newnode(r-mid);
		if(x<=mid) change(tr[u1].ls,tr[u2].ls,l,mid,x);
		else change(tr[u1].rs,tr[u2].rs,mid+1,r,x);
		pushup(u1); pushup(u2);
	}
	void main() {
		sf("%d%d",&n,&m);
		rep(i,1,n) {
			int l,r,p;
			sf("%d%d%d",&l,&r,&p);
			a[i]={i,l,r,p};
			ap[i]=p;
		}
		sort(ap+1,ap+n+1);
		m = unique(ap+1,ap+n+1)-ap-1;
		rep(i,1,n) a[i].p = lower_bound(ap+1,ap+m+1,a[i].p) - ap;
		bool ans=1;
		sort(a+1,a+n+1,cmpr);
		int rx=lim+1,it=n+1;
		ar[it]=rx;
		lx=1;
		per(i,n,1) {
			if(a[i].r<rx) rx=a[i].r, it=i;
			else --rx;
			if(rx==0) lx=i+1, rx=-1;
			ar[i]=rx;
			while(ar[it]>a[i].r) --it;
			a[i].r=it;
		}
		sort(a+1,a+n+1,cmpl);
		it=lx;
		rep(i,1,n) {
			while(ar[it]<a[i].l) ++it;
			a[i].l=it;
		}
		rep(i,1,m) rt[i]=cnt++, tr[rt[i]].newnode(n-lx+1);
		per(i,n,1) {
			int l=a[i].l, r=a[i].r, p=a[i].p;
			a[i].r=insert(rt[p],lx,n,l,r);
			if(!a[i].r) {
				ans=0;
				break;
			}
		}
		if(!ans) {
			puts("NIE");
			exit(0);
		}
		sort(a+1,a+n+1,cmpr);
		cnt=0;
		rt[0]=cnt++, tr[rt[0]].newnode(0);
		rep(i,1,m) rt[i]=cnt++, tr[rt[i]].newnode(n-lx+1);
		rep(i,1,n) {
			int l=a[i].l, r=a[i].r, p=a[i].p;
			int pos2=insert2(rt[0],rt[p],lx,n,l,r);
			if(!pos2) {
				change(rt[0],rt[p],lx,n,r);
				anss[a[i].id]=r;
			} else anss[a[i].id]=pos2;
		}
		pf("%d\n",tr[rt[0]].s);
		rep(i,1,n) pf("%d\n",ar[anss[i]]);
	}
}
int main() {
	#ifdef LOCAL
	freopen("in.txt","r",stdin);
	freopen("my.out","w",stdout);
	#endif
	wing_heart :: main();
}

更正解的做法(Wuyanru 题解的做法)

看了第一篇题解,怎么这么牛。

首先按照前面所说更新 \(r_i\)

从小到大枚举时间 \(t\)

每个器材维护一个 set。

\(l_i = t\),把 \(i\) 加到 \(p_i\) 的 set 里面。

\(r_i = t\),那么 \(i\)\(r_i\) 时刻健身,然后在其他非空的器材的 set 里面选择 \(r\) 最小的人在这一天健身。

哇这个做法太牛了,我的卡常做法太蠢啦,我要写一发代码,以验证我有没有理解错。

时间复杂度 \(O(n \log n)\),空间复杂度 \(O(n)\)。还好写,不用卡常。

#include<bits/stdc++.h>
#define sf scanf
#define pf printf
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
using namespace std;
typedef long long ll;
namespace wing_heart {
	constexpr int N=1e6+7,lim=1e9;
	constexpr ll m1=1e6+1;
	int n,m,lx;
	struct piii {
		int l,r,p;
	}a[N];
	int al[N],ar[N];
	int ans[N];
	bool cmpl (int x,int y) { return a[x].l < a[y].l; }
	bool cmpr (int x,int y) { return a[x].r < a[y].r; }
	int ap[N]; 
	bool fl;
	struct pii {
		int l,r;
		bool operator < (const pii b) const { return l == b.l ? r < b.r : l < b.l; }
	};
	set<pii> tr[N];
	struct pi {
		int id;
		bool operator < (const pi b) const { return a[id].r == a[b.id].r ? id < b.id : a[id].r < a[b.id].r; }
	};
	set<pi> tr2[N];
	void main() {
		sf("%d%d",&n,&m);
		rep(i,1,n) {
			int l,r,p;
			sf("%d%d%d",&l,&r,&p);
			a[i]={l,r,p};
			ap[i]=p;
			al[i]=ar[i]=i;
		}
		
		sort(ap+1,ap+n+1);
		m = unique(ap+1,ap+n+1)-ap-1;
		rep(i,1,n) a[i].p = lower_bound(ap+1,ap+m+1,a[i].p) - ap;
		
		fl=1;
		sort(al+1,al+n+1,cmpl);
		rep(i,1,m) tr[i].insert({1,lim});
		per(i,n,1) {
			int id=al[i];
			int l=a[id].l, r=a[id].r, p=a[id].p;
			auto it = tr[p].upper_bound({r,lim});
			if(it==tr[p].begin()) {
				fl=0;
				break;
			}
			--it;
			if(it->r < l) {
				fl=0;
				break;
			}
			int x=min(r,it->r);
			a[id].r=x;
			pii tmp=*it;
			tr[p].erase(it);
			if(tmp.l<=x-1) tr[p].insert({tmp.l,x-1});
			if(x+1<=tmp.r) tr[p].insert({x+1,tmp.r});
		}
		if(!fl) {
			puts("NIE");
			exit(0);
		}

		sort(ar+1,ar+n+1,cmpr);
		int it=1;
		vector<int> vec;
		rep(i,1,n) {
			int id=ar[i];
			if(ans[id]) continue;
			++ans[0];
			int r=a[id].r;
			while(it<=n && a[al[it]].l <= r) {
				int x=al[it];
				tr2[a[x].p].insert({x});
				if(tr2[a[x].p].size()==1) vec.push_back(a[x].p);
				++it;
			}
			vector<int> tmp;
			for(int x : vec) {
				ans[tr2[x].begin()->id]=r;
				tr2[x].erase(tr2[x].begin());
				if(!tr2[x].empty()) tmp.push_back(x);
			}
			vec=tmp;
		}
		pf("%d\n",ans[0]);
		rep(i,1,n) pf("%d\n",ans[i]);
	}
}
int main() {
	#ifdef LOCAL
	freopen("in.txt","r",stdin);
	freopen("my.out","w",stdout);
	#endif
	wing_heart :: main();
}
posted @ 2025-09-25 21:36  wing_heart  阅读(29)  评论(0)    收藏  举报