题解:洛谷 P13904 (「KFCOI Round #2」夏日·弥光)

1. Description

有一个长度为 \(n\) 的序列,第 \(\forall i\in[1, n]\) 个位置上有权值 \(p_i\) 和能量值 \(a_i\)

一开始你的能量值,疲劳值和贡献值均为 \(0\),可以从序列上的任意位置出发。

假定当前你位于第 \(x\) 个位置,拥有 \(t\) 的能量值,疲劳值为 \(k\),那么可以获得 \(\lfloor \frac{p_x}{2^k} \rfloor\) 的贡献值。

接着,你可以同时对自己的能量值和疲劳值分别进行操作: \(t\leftarrow a_x\)\(k\leftarrow 0\),也可以不操作。

然后,在 \(x + t \le n\) 并且 \(t \not= 0\) 的时候,你可以移动到 \(x + t\) 的位置,并使得疲劳值 \(k\leftarrow k + 1\),也可以不移动,然后结束所有操作。

请求出从某个点出发的最大贡献值之和。

2. Solution

应该用 dp 解题,但是如果你写出一个暴力的 \(f_{i,t,k}\) 来表示在 \(i\) 这个位置,能量值为 \(t\),疲劳值为 \(k\) 的最大收益,那么就不好做了。所以我们考虑压掉两维状态,套路的,我们定义 \(f_i\) 表示从 \(i\)\(i\) 之前出发,强制在 \(i\) 的位置进行一次操作,可以得到的最大收益。

那么转移显然:\(f_i=\max_{a_j\mid (i-j)}(f_j+\sum_{k=0}^{\frac{i-j}{a_j}}\frac{p_{j+ka_j}}{2^k})\),采用刷表法转移,后面的 \(\sum_{k=0}^{\frac{i-j}{a_j}} \frac{p_j+ka_j}{2^k}\) 可以在转移过程中一并处理出来,时间复杂度就是 \(O(\sum_{i=1}^n\frac{n-i}{a_i})\)。而看到 \(\frac{n-i}{a_i}\) 这种时间复杂度,我们不难想到根号分治。

如果 \(\frac{n-i}{a_i}\le \sqrt n\) 的位置,我们可以直接暴力跑刷表法,时间复杂度为 \(O(n\sqrt n)\),可以通过。

对于 \(\frac{n-i}{a_i}>\sqrt n\),我们可以得到的只有 \(a_i< \sqrt n\) 这个性质,所以要根据这个性质来优化转移,也就需要进一步挖掘转移的性质。

根据上面的转移式子,我们不难发现两个性质:

  1. \(j\) 可以转移到 \(i\) 时必然满足 \(a_j\mid i-j\),也就是满足 \(i\equiv j\pmod {a_j}\)
  2. 由于每个位置的贡献是 \(\lfloor \frac{p_x}{2^k}\rfloor\),所以其实最多跳了 \(30\) 步之后,\(f_j+\sum_{k=0}^{\frac{i-j}{a_j}}\frac{p_{j+ka_j}}{2^k}\) 的值就就不再发生变化了。

所以,我们对于 \(\frac{n-i}{a_i}>\sqrt n\) 的位置首先暴力转移 \(30\) 步,同时计算出不再发生变化的 \(val=f_j+\sum_{k=0}^{\frac{i-j}{a_j}}\frac{p_{j+ka_j}}{2^k}\) ,然后然后将 \((j,val)\) 这一二元组储存在第 \(31\) 步会转移到的位置。

然后在转移过程中,我们维护一个最值数组 \(mx_{i,j}\) 表示在当前位置之前,下标模 \(i\) 等于 \(j\) 的所有位置中可以得到的 \(val\) 的最大值是多少,接着当我们遍历到 \(x\) 这个位置的时候,遍历 \(mx_{i,x\bmod i}\)\(f_x\) 进行更新,最后再进行转移即可。

时间复杂度为 \(O(n\sqrt n)\),当然,由于第二种转移的常数比较大,所以可以适当的调大块长来平衡时间复杂度。

3. Code

/*by qwer6*/
/*略去缺省源与快读快写*/
const int N=5e5+5,M=715;
int n;
ll ans;
int p[N],a[N];
ll f[N],mx[M][M];
struct Modify{
	int x,y;
	ll val;
};
vector<Modify>modify[N];
signed main(){
	read(n);
	for(int i=1;i<=n;i++)f[i]=read(p[i]),read(a[i]);
	int B=1500,C=n/B+1;
	for(int i=1;i<=n;i++){
		for(auto tmp:modify[i])
			tomax(mx[tmp.x][tmp.y],tmp.val);
		for(int j=1;j<C;j++)
			tomax(f[i],mx[j][i%j]);
		tomax(ans,f[i]);
		if((n-i)/a[i]<=B){
			ll val=0,k=1;
			for(int j=i+a[i];j<=n;j+=a[i],k++){
				if(k<=30)val+=p[j]>>k;
				tomax(f[j],f[i]+val);
			}	
		}else{//现在这里至少可以跳 30 次  
			ll val=0,j=i+a[i];
			for(int k=1;k<=30;k++,j+=a[i]){
				val+=p[j]>>k;
				tomax(f[j],f[i]+val);
			}
			if(j<=n)
				modify[j].push_back({a[i],i%a[i],f[i]+val});
		}
	}
	write(ans);
}

4.后记

—师兄你是喜欢小龙女么?

—你们都叫她小龙女么?

posted @ 2025-11-28 09:36  陈牧九  阅读(0)  评论(0)    收藏  举报