CF2091G Gleb and Boating
Problem
程序员 Gleb 经常访问 IT Campus "NEIMARK" 参加编程训练。
Gleb 不仅是程序员,还是一位著名的划船运动员,因此他选择通过划皮划艇沿河流完成部分通勤路程。假设 Gleb 从点 \(0\) 出发,必须到达点 \(s\)(即沿直线划行 \(s\) 米)。为增加挑战性,Gleb 决定不离开线段 \([0, s]\)。皮划艇的尺寸可忽略不计。
Gleb 是实力强劲的程序员!初始时他的力量为 \(k\)。Gleb 的力量直接影响皮划艇的运动:若当前力量为 \(x\),则每次划桨可使皮划艇沿当前方向移动 \(x\) 米。Gleb 可以调头并继续向相反方向移动,但此操作十分困难,每次调头后力量会减少 \(1\)。力量永远不会变为 \(0\) —— 若当前力量为 \(1\),则即使调头后仍保持 \(1\)。此外,Gleb 不能连续两次调头 —— 每次调头后必须至少移动一次才能再次调头。同理,Gleb 不能在出发后立即调头 —— 必须先进行一次划桨。
Gleb 希望在从点 \(0\) 到达点 \(s\) 的过程中不离开线段 \([0, s]\) 并尽可能保留最多力量。请帮助他 —— 给定 \(s\) 和初始力量 \(k\),确定到达点 \(s\) 时可能保留的最大力量。
输入格式
每个测试包含多个测试用例。第一行包含测试用例数量 \(t\) (\(1 \leq t \leq 100\))。接下来是测试用例描述。
每个测试用例单独一行,包含两个整数 \(s\) 和 \(k\) (\(1 \leq s \leq 10^9\),\(1 \leq k \leq 1000\),\(k \leq s\))。
保证所有测试用例的 \(k\) 之和不超过 \(2000\)。
输出格式
对于每个测试用例,输出 Gleb 在旅程结束时可能保留的最大力量。
输入输出样例 #1
输入 #1
8
9 6
10 7
24 2
123456 777
6 4
99 6
10 4
99 4
输出 #1
4
1
2
775
1
4
2
2
说明/提示
第一个样例中 Gleb 的一种可能移动方式:

Solve
通过样例得知我们可以通过减小2点体力来获得小于k的位移
比如我先往前走k,再掉头走k-1,再转回来k-2,这样共计前进k-1格,即回退1格
那我们走k-1时走n步,回来也能退n步,可以通过这样微调到终点
但是很多情况s比较小,没办法“展开拳脚”,所以只有s比较大才行
因为一次走k,所以最多要回退k-1,随便算算发现\(s>k^2\)时一定可以通过这种方式到n
然后再看看能否直接走到s,答案就出来了
这样我们只需要看\(s\le k^2\)就行了,也就是说\(s\le 10^6\)
此时我们可以DP,设\(f_{i,j}=0/1\)为体力为i时能否走到j
动态转移方程就是(刷表法)$$f_{i,j}\rightarrow f_{i,j+i}$$$$f_{i,j}\rightarrow f_{i-1,j-k+1}$$
第二个很好理解,第一个可以参考完全背包
当然如果j不在0-n之间就不能转移了,而且这个仅仅是当前方向面对终点的转移,面朝起点需要倒着推
然后在推完一层的时候判断位置s是不是1就行了
这样时间复杂度为\(O(sk)\),1e9规模会TLE……吗?
仔细思考,答案似乎几乎不会小于\(\sqrt s\),因为此时根据前面的结论可以直接通过体力减小2来得到答案,也就是说最多推\(k-\sqrt s\)层,时间复杂度\(O(s(k-\sqrt s))\),可以通过
最后记得加滚动数组!
Code
#include<bits/stdc++.h>
using namespace std;
int T,s,k;
bool f[2][1000005],l;
int main(){
cin>>T;
while(T--){
l=0;
cin>>s>>k;
if(s%k==0){
cout<<k<<endl;
continue;
}
if(k*k<=s){
cout<<max(k-2,1)<<endl;
continue;
}
for(int i=0;i<=s;i++){
f[0][i]=(i%k==0);
f[1][i]=0;
}
k++;
while(k--){
if(!l){
for(int i=0;i<=s;i++){
if(f[l][i]==1){
if(i+k<=s)f[l][i+k]=1;
if(i-k+1>=0)f[!l][i-k+1]=1;
}
}
}else{
for(int i=s;i>=0;i--){
if(f[l][i]==1){
if(i-k>=0)f[l][i-k]=1;
if(i+k-1<=s)f[!l][i+k-1]=1;
}
}
}
if(f[l][s]||k==1){
cout<<k<<endl;
break;
}
for(int i=0;i<=s;i++){
f[l][i]=0;
}
l=!l;
}
}
return 0;
}

浙公网安备 33010602011771号