CF 311C

题目

\(h\) 个房间,其中 \(n\) 个有宝藏,第 \(i\) 个宝藏房编号为 \(a_i\),里面宝藏价值为 \(c_i\) 。初始位置为 \(1\),每次可以跨越 \(k\) 个房间,可以移动无数次。

\(m\) 次操作,形如:

\(1\) \(x\),表示增加一个技能,每次可以跨越 \(x\) 个房间

\(2\) \(x\) \(y\) ,表示让第 \(x\) 个房间宝藏价值减少 \(y\)

\(3\) ,查询当前从 \(1\) 出发能到达的所有房间中宝藏价值最大的房间,并 拿走 它的宝藏,如果有多个最大值,任意拿走一个。

数据范围:

\(h\leq 10^{18},n\leq 10^5,k\leq 10^4,a_i\leq h,c_i\leq 10^9\)
保证 \(1\) 操作不超过 \(20\) 次,\(2\) 操作不会减成负数,并且 \(2\) 操作不会减少已经拿走的宝藏。

题解

发现 \(k\) 很小,那么考虑将原序列分成若干个模 \(k\) 同余的组,那么组内可以从前往后到达,并且将序列中每 \(k\) 个分成一段。

那么现在发现每个组想要知道的信息仅为,该组最早能在哪一段被到达,因为前面的一个能走到,那么后面的都能走到了。

考虑每次增加的技能 \(x\) ,相当于可以从 第 \(i\) 组走到 \((i+x)\mod k\) 组,跨越了 \((i+x)/k\) 个段,那么每次增加技能我们都相当于从 \(i\)\((i+x)\mod k\) 连一条权值为 \((i+x)/k\) 的段,每次都重新跑一遍最短路,就可以知道每一组最少走多少段被到达。每次新增的能到达的点直接加到 \(\text{set}\) 里就好了。

小细节:因为保证了 \(2\) 操作不会减少已经拿走的宝藏,那么 \(\text{set}\) 里面只维护值,二操作每次直接删就行了。不用维护标号。。刚开始被翻译坑了

const int N=2e5+5;
const int M=10050;
ll h,a[N];
int n,m,k,c[N],op,x,y;
multiset<pii>s;
vector<pii> v[M];
bool vis[N],pd[N],cd[N];
int dis[N];
void spfa(int tmp){
	mem0(vis);
	mem0x3f(dis);
	queue<int>q;
	q.push(tmp);dis[tmp]=0;
	while(!q.empty()){
		auto u=q.front();
		q.pop();
		vis[u]=0;
		for(auto i:v[u]){
			if(dis[i.fi]>dis[u]+i.se){
				dis[i.fi]=dis[u]+i.se;
				if(!vis[i.fi]){
					q.push(i.fi);vis[i.fi]=1;
				}
			}
		}
	}
}
void solve(){
	for(int i=0;i<k;i++){
		v[i].pb(mp((i+x)%k,(i+x)/k));
	}
	spfa(1%k);
	for(int i=1;i<=n;i++){
		if(!pd[i] && dis[a[i]%k]<=(a[i]/k)){
			pd[i]=1;
			s.insert(mp(c[i],i));
		}
	}
}
signed main(){
	#ifdef newbiewzs
	#else
	#endif
	cin>>h;n=read();m=read();k=read();
	for(int i=1;i<=n;i++){
		a[i]=read();c[i]=read();
	}
	for(int i=1;i<=n;i++){
		if(a[i]%k==1%k){
			pd[i]=1;
			s.insert(mp(c[i],i));
		//	cerr<<"sesese:"<<c[i]<<endl;
		}
	}
	for(int i=1;i<=m;i++){
		op=read();
		if(op==1){
			x=read();
			solve();
		}
		if(op==2){
			x=read();y=read();
			if(pd[x]){
				auto it=s.lower_bound(mp(c[x],x));
				s.erase(it);
			}
			c[x]-=y;
			if(pd[x]){
				s.insert(mp(c[x],x));
			}
		}
		if(op==3){
			if(!s.size()){
				puts("0");
			}
			else{
				auto it=--s.end();
				cout<<(*it).fi<<'\n';
				cd[(*it).se]=1;
				s.erase(it);
			}
		}
	}
	return 0;
}
posted @ 2021-10-09 11:33  newbiewzs  阅读(52)  评论(1)    收藏  举报