*题解:CF2091G Gleb and Boating

题目链接

解析

考虑 DP。

最朴素的方法是设 \(f_{i,j}\) 表示是否可能位于位置 \(i\) 的同时力量值为 \(j\)。若力量值为 \(j\) 时应当向左走,则:

\(f_{i,j} \leftarrow f_{i + j,j} \operatorname{or} f_{i + j,j + 1}\)

向右走时同理,时间复杂度 \(O(sk)\)。

我们来挖掘一下性质。设当前位置为 \(p\) ,力量值为 \(x\),那么若 \(x \mid s - p\),则 \(x\) 是合法解。对于移动,考虑我们调一次头走一下再调回来走一下,那么位置就由 \(p\) 变为 \(p - (x - 1) + (x - 2) = p - 1\),也就是相对于原位置左移一步。更一般地,我们有 \(p - a(x - 1) + a(x - 2) = p - a\)。那么这个有什么用呢?左移可以使 \((s - p) \bmod (x - 2)\) 的值加一,所以,如果 \(s\) 足够大,那么我们就可以通过左移操作来使得 \((s - p) \bmod (x - 2) = 0\),即令 \(x - 2\) 为一个解。你说这 \(s\) 要多大才算大呀,最劣时以 \(x\) 的力量值往右走完后 \((s - p) \bmod (x - 2) = 0\),也就是要左移 \(x - 2\) 步,考虑最开始往右走 \(x - 2\) 次,那么移动期间最远到达 \(x\cdot (x - 2) = x^2 - 2x\)。

故当 \(s \ge k ^ 2 - 2k\) 时,\(k - 2\) 必定为一个解,只需再判断是否有 \(k\mid s\) 就能求出答案。

对于 \(s < k ^ 2 - 2k\) 的情况,不妨回到朴素 DP,直接做的时间复杂度是 \(O(k ^ 3)\) 但是实测跑的飞快。这是怎么回事呢?其实是因为 \(k\) 越走越小,走到大概 \(\sqrt s\) 的时候就变为了上面的那种情况,常数小。

想到了的地方

枚举答案。朴素 DP。

\(x - (x - 1) + (x - 2) = x - 1\)

没想到的地方

尽量往右走以让可以左移(改余数)的次数变多。

按照 \(s\) 的大小分类讨论。

为什么没想到

有点忘了,以后这部分得先写。

依旧零件找到了不会拼。

没有充分利用 \(x - (x - 1) + (x - 2) = x - 1\) 中 \(x\) 的系数。

代码

/*
考虑枚举答案
能否恰好到达点 s 取决与当前位置 pos 和力量值 k 是否满足 k | s - pos
设 x = (s - pos) % k,调头之后变为 (s - pos) % (k - 1) 
并不是很好直接转换

若当前力量为 k 且要调头, 

考虑朴素 dp,设 f[i][j] 表示当前在位置 i,力量值为 j 是否可行

f[i][j] |= f[i + j][j] / f[i - j][j]

f[i][j] |= f[i + j + 1][j + 1] / f[i - j - 1][j + 1]  

要判断答案 k 是否可行,我们只关心余数

f[i][j] 表示当前位置离终点的距离 % k = i,力量值为 j 是否可行

只能做到三次方 

单调性?

a[1] * k - a[2] * (k - 1) + a[3] * (k - 2) - a[4] * (k - 3) + ... + a[x] * (k - x + 1)

a[1] * (k - x + 1) + a[1] * (x - 1)

前一项取模的时候直接丢掉,只关心后一项之和能达到的余数 

x - (x - 1) + (x - 2) = x - 1 

也就是说绕一圈回来的效果是左移一次 
连续左走两次右走两次就是左移两次 
调 x 的系数来避免出界  

对于答案 r,需要左移 t = (r - S % r) 次,若 S % r = 0 就不用左移

设 f[i][j] 表示力量值为 i,左移了 j 次 
*/
#include <bits/stdc++.h>
#define ls(x) ((x) << 1)
#define rs(x) (((x) << 1) | 1)
#define mid ((l + r) >> 1)
using namespace std;
typedef long long ll;
typedef pair<ll,int> pii;
const int N = 1000000 + 1005,M = 1e6 + 5,mod = 998244353;
int f[2][N];
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T;
	cin>>T;
	while(T--){
		int s,k;
		cin>>s>>k;
		if(s >= k * (k - 2) || s % k == 0){
			cout<<(s % k == 0 ? k : max(1,k - 2))<<'\n';
			continue;
		}
		for(int i=0;i<=s;i += k){
			f[k & 1][i] = true;
			
		}
		bool flag = false;
		for(int i=k - 1;i>=1;i--){
			int b = i & 1;
			if((k - i) & 1){
				for(int j=s;j>=0;j--){
					f[b][j] = f[b][j + i] | f[b ^ 1][j + i];	
				}
			}else{
				for(int j=0;j<=s;j++){
					if(j >= i){
						f[b][j] = f[b][j - i] | f[b ^ 1][j - i];
					}else{
						f[b][j] = 0;
					}
					
				}
			}
			if(f[b][s]){
				flag = true;
				cout<<i<<"\n";
				break;
			}
		}
		if(!flag){
			cout<<1<<'\n';
		}
		for(int i=0;i<=s;i++){
			f[0][i] = f[1][i] = 0;
		}
	}
	return 0;
}
posted @ 2026-09-27 17:27  yutar  阅读(2)  评论(0)    收藏  举报