P90241 [炼石计划--NOIP模拟五]--打怪兽
题面
小 D 在玩一个打怪兽的游戏,这个游戏的地图可以看做一个 n 个点 m 条边的无向图,在除了一号点之外的所有点上分别有一个等级为 $w_i$ 的怪兽。小 D 开始时处于 1 号节点,等级为 $w_1$每天开始后,小 D 从 1 号节点开始,经过若干没有怪兽的节点到达一个有怪兽的节点 u,并击杀 u 节点的怪兽。玩家只能击杀等级比自己严格更低的怪兽,且每天只能击杀一个怪兽。
小 D 每击杀一个怪兽,他的等级会升高 A,而每天开始时,所有存活怪兽的等级会升高 B。小 D 想知道他至少需要多少天才能击杀n 节点的怪兽并通关,如果永远不能完成,输出 −1
额 容我思考一下
还没写完我回来了!
来这里的都是同营人吧
分成两种情况
- A<=B
人越变越菜(原话 - A>B
额怎么形容?人会变强?
整体思路
游戏规则:从1号点出发,每天可以选择击杀一个怪兽(等级严格低于当前等级),击杀后等级升高A,每天结束时所有存活怪兽等级升高B。目标是击杀n号点的怪兽。
关键变量
dis[i]:击杀i号点怪兽的最短天数L[i]:击杀i号点怪兽所需的最早天数
剩下的应该都知道~
A ≤ B 时
这时遇到一个可以打的怪 我们就赶紧打(可以收获微乎其微的A ,更为重要的是现在不打以后可能打不过了
那么我们相当于可以做一个bfs(因为路是一样长的
如果一个点不能被打败(那么他永远都不可能被打败,因为他只会越来越强,那么这个点就走不了,这里不要遍历
我们用bfs求出到达每个点的最小天数
到n的最小天数就是答案
额 我们怎么去看一个点可以不可以被打败?(看下方:
对于当前节点u,已经花了dis[u]天到达并击杀u:
- 玩家等级:
w[1] + dis[u] * A(击杀了dis[u]个怪兽) - 注意:在BFS路径上,每个节点都代表一次成功击杀
考虑邻居v,需要在第dis[u]+1天击杀v:
- 玩家等级:
w[1] + dis[u] * A - 怪兽v等级:
w[v] + (dis[u]+1) * B - 击杀条件:
w[1] + dis[u] * A > w[v] + (dis[u]+1) * B - 化简:
w[1] + dis[u] * (A-B) - B > w[v]
其实这个地方我好像推不出来 等我明天去问老师
推出来了 到v的时候等级还是打败u的时候
这部分的代码
点击查看代码
for(int i=1;i<=n;i++) dis[i]=0x3f3f3f3f,vis[i]=false;
queue<int>q;
dis[1]=0;
vis[1]=true;
q.push(1);
while(!q.empty()){
int u=q.front();q.pop();
for(int i=h[u];~i;i=ne[i]){
int v=e[i];
if(vis[v]) continue;
if(w[1]+(A-B)*dis[u]-B>w[v]){//这就是上面的柿子
vis[v]=true;//能打败才继续走这个点
dis[v]=dis[u]+1;
q.push(v);
}
}
}
A > B 时
感觉这部分比上面的难欸
还是类似于dij(和上面有点像
主要看怎么维护这个堆,什么点可以走
Notice:
L[i]:表示玩家需要至少多少天才能击杀怪兽iL[i] = (w[i] - w[1] + A) / (A - B) + 1
推导:- 第t天玩家等级:
w[1] + A*t - 怪兽i等级:
w[i] + B*t - 需要:
w[1] + A*t > w[i] + B*t - 即:
(A-B)*t > w[i] - w[1] - 所以:
t > (w[i] - w[1]) / (A-B) - 取整后加1得到
L[i]
- 第t天玩家等级:
类Dij的过程:
- 每次取出堆最小的节点u
- 更新:
dis[v] = min(dis[v], max(dis[u]+1, L[v]))- 这表示:从u到v至少要花dis[u]+1天,但必须等到第L[v]天才能击杀v
- 特殊情况:如果
cur < dis[u]-1就把这个点设为inf
cur 的作用就是标记当前已经处理到第几天了
这部分的代码
点击查看代码
for(int i=1;i<=n;i++) dis[i]=0x3f3f3f3f,vis[i]=false;
for(int i=2;i<=n;i++){
L[i]=(w[i]-w[1]+A)/(A-B)+1;
if(L[i]<=0) L[i]=1;
}
priority_queue<PII,vector<PII>,greater<PII>>q;
dis[1]=0;
q.push({0,1});
int cur=-1;
while(!q.empty()){
PII t=q.top();q.pop();
int u=t.second;
if(vis[u]) continue;
vis[u]=true;
if(cur<dis[u]-1){
dis[u]=0x3f3f3f3f;
continue;
}
cur++;
for(int i=h[u];~i;i=ne[i]){
int v=e[i];
if(vis[v]) continue;
int nd=max(dis[u]+1,L[v]);
if(nd<dis[v]){
dis[v]=nd;
q.push({dis[v],v});
}
}
}
好像要加快读?
那给一个很快的快读板子吧(是真的很快
点击查看代码
char *p1,*p2,buf[100000];
#define nc() (p1==p2 && (p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
int read(){
int x=0,f=1;
char ch=nc();
while(ch<48||ch>57){
if(ch=='-') f=-1;
ch=nc();
}
while(ch>=48&&ch<=57){
x=x*10+ch-48;
ch=nc();
}
return x*f;
}
void write(int x){
if(x<0){
putchar('-');
x=-x;
}
if(x>9) write(x/10);
putchar(x%10+'0');
}

浙公网安备 33010602011771号