*题解: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;
}

浙公网安备 33010602011771号