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:"<