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\),这样走一定不是最优的:

\[2 \to 3 \to 1 \to 4 \]

这样吃掉了 34 两个点。然而我们可以这么走:

\[2 \to 1 \to 4 \to 3 \]

这样吃掉了 432 三个点。可以看到,我们在 2 如果选择跳 \(b\),就会白白浪费掉 \(a_2\),并且在 2 的左侧完全有一个 1 能够跳到更远的位置。

或者说,最后的路径上不会出现“外面套着里面”的形状,一定是“相扣”的。

这样一来,我们就能够利用 \(i\)\(b_i\) 中间只有一个从前面跳过来的 \(b\) 进行转移。

\[f_{b_i}= \min_{j=i}^{b_i-1}f_j+a_i \]

看到这个单点修改、区间求 \(\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);
posted @ 2024-10-23 17:59  Jordan_Pan  阅读(8)  评论(0编辑  收藏  举报