CF1131G Most Dangerous Shark

题解

Posted by WrongAnswer_90's Blog on March 13, 2024

CF1131G Most Dangerous Shark

更好的阅读体验

题解区大佬的题解都看不懂捏。提供一个可能更简单的思路?

首先可以线性求出 $pre_i,nex_i$ 表示向左,向右能推倒的最远的骨牌的下标,以求 $pre$ 为例,具体方法是从小到大扫 $i$,初始化 $j$ 为 $i-1$,然后不断执行如果 $i-j<a_i$ 则令 $j$ 变成 $pre_j-1$,否则 $pre_i$ 为 $j+1$,求 $nex$ 同理。

正确性显然,复杂度是均摊 $\mathcal O(n)$,证明的话考虑扫到 $i$ 的时候,设当前的势能 $\varphi(i)$ 表示从 $i$ 开始,跳 $pre$ 跳几次会跳到 $1$,每次势能最多增加 $1$,跳了几次 $pre$ 势能就会减少多少,而势能显然是非负的。

有了 $pre$ 和 $nex$ 之后就可以 dp 了,设 $f_i$ 为推倒前 $i$ 块最小代价,则

\[f_i=\min_{pre_i-1\leq j} f_j+c_i\]

然后用 $f_{i-1}+c_i$ 更新 $f_{nex_i}$ 即可,瓶颈在前 $pre$ 的部分,看起来只能带 $\log$,但是由于 $pre$ 的特殊性质,可以优化到 $\mathcal O(n)$。

考虑 $\log$ 怎么做,线段树太平凡了,考虑单调栈,二分查询。但是注意到 $pre$ 有个很强的性质:$[pre_i,i]$ 不会出现交叉不包含的情况,所以对于查询到的一个元素,当前单调栈中该元素之后的所有元素都一定不会被查询到,可以直接弹掉,这样就不需要二分了,总复杂度均摊 $\mathcal O(n)$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
	int n,m,q,top,f[10000010],st[10000010],pre[10000010],nex[10000010];
	pii a[10000010];
	vector<pii> ve[250010];
	inline void mian()
	{
		read(n,m),m=0;int x,y;
		for(int i=1;i<=n;++i)
		{
			read(x),ve[i].resize(x);
			for(int j=0;j<x;++j)read(ve[i][j].fi);
			for(int j=0;j<x;++j)read(ve[i][j].se);
		}
		read(q);
		while(q--){read(x,y);for(auto p:ve[x])a[++m]=p,a[m].se*=y;}
		for(int i=1,j=0;i<=m;pre[i++]=j+1,j=i-1)while(j&&i-j<a[i].fi)j=pre[j]-1;
		for(int i=m,j=m+1;i>=1;nex[i--]=j-1,j=i+1)while(j<=m&&j-i<a[i].fi)j=nex[j]+1;
		memset(f,127,sizeof(f)),f[0]=0;
		for(int i=1;i<=m;++i)
		{
			while(top&&st[top-1]>=pre[i]-1)--top;
			Mmin(f[i],f[st[top]]+a[i].se),Mmin(f[nex[i]],f[i-1]+a[i].se);
			while(top&&f[st[top]]>=f[i])--top;
			st[++top]=i;
		}
		write(f[m]);
	}