CF2023B 题解
首先要读懂题目:每次机器选择的是小于且先前没操作过的最大位置,机器的选择是唯一的。
这道题看上去就很贪心或者 dp,然而我太菜了两条都走到了死路。然而我们还是能注意到一些性质的。
从一个点开始一直选择吃掉 \(a_i\),最后会走到 \(1\) 号点,并且把 \(1\) 到 \(i\) 上的所有还没吃过的点全部吃掉。
注意到 \(a_i>0\),那么我们肯定是拿的点要尽量多,这时如果 \(b_i \le i\),那选择跳 \(b_i\) 就是很不优的,因为这样就把 \(a_i\) 放弃了,而选择一个一个地吃过去也能到达同样的位置,并且把 \(a_i\) 吃掉了。于是最后的路径大概是这样的:向右跳 \(b\),向左吃几个(可能是 0 个),向右跳 \(b\),向左吃几个……最后走到一个最右边的点,往前把没吃过的点全部吃掉。
发现吃掉点的最大值很不好维护,正难则反,考虑维护放弃的最小值。把能到达最右侧的点为 \(i\) 时放弃的点的最小值设为 \(f_i\),则用前缀和减掉他就是吃掉点的最大值,最后取 \(\max\) 即可。
到这里已经可以直接建图跑最短路,然后就做完了。
具体地,向 \(i-1\) 连一条权值为 \(0\) 的边,向 \(b_i\) 连一条权值为 \(a_i\) 的边,跑出来到 \(1\) 到 \(i\) 的最短路就是 \(f_i\)。代码实现用了 Dij。
rd(n);
for(int i=1;i<=n;i++)
g[i].clear(),dis[i]=1e18;
for(int i=1;i<=n;i++)rd(a[i]),a[i]+=a[i-1];
for(int i=1;i<=n;i++){
rd(b[i]);
g[i].push_back({b[i],a[i]-a[i-1]});
if(i>1)g[i].push_back({i-1,0});
}
q.push({dis[1]=0,1});
for(;!q.empty();q.pop()){
long long ww=q.top().first;
int u=q.top().second;
if(dis[u]<ww)continue;
for(auto i:g[u]){
int v=i.first,w=i.second;
if(dis[u]+w<dis[v])q.push({-(dis[v]=dis[u]+w),v});
}
}
ans=0;
for(int i=1;i<=n;i++)
ans=std::max(ans,a[i]-dis[i]);
printf("%lld\n",ans);
然而这个菜鸡太菜了,没想到这个做法……所以我是怎么写出来的
我们继续思考最后的路径还有哪些性质,发现向左吃的时候一定不会超过上一个放弃掉的点。同时也是从这个点跳到此时路径最右侧,也就是上一个跳 \(b\) 的点。
什么意思呢,如果 \(b_1=4\),\(b_2=3\),这样走一定不是最优的:
这样吃掉了 3
和 4
两个点。然而我们可以这么走:
这样吃掉了 4
,3
,2
三个点。可以看到,我们在 2
如果选择跳 \(b\),就会白白浪费掉 \(a_2\),并且在 2
的左侧完全有一个 1
能够跳到更远的位置。
或者说,最后的路径上不会出现“外面套着里面”的形状,一定是“相扣”的。
这样一来,我们就能够利用 \(i\) 到 \(b_i\) 中间只有一个从前面跳过来的 \(b\) 进行转移。
看到这个单点修改、区间求 \(\min\) 的东西,这个菜鸡想到了线段树。注意不能走到的点是不能继续转移的。
rd(n);
for(int i=1;i<=n;i++)rd(a[i]),a[i]+=a[i-1];
for(int i=1;i<=n;i++)rd(b[i]);
Bld(1,1,n);Chg(1,1,n,1,0);
ans=a[1];
for(int i=1;i<=n;i++){
if(b[i]<=i)continue;
long long val=Qry(1,1,n,i,b[i]-1);
if(val>1e15)continue;
val+=a[i]-a[i-1];
Chg(1,1,n,b[i],val);
ans=std::max(ans,a[b[i]]-val);
}
printf("%lld\n",ans);