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;
multisets;
vector v[M];
bool vis[N],pd[N],cd[N];
int dis[N];
void spfa(int tmp){
	mem0(vis);
	mem0x3f(dis);
	queueq;
	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>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:"<