传送门
有 m m m 个小球, n n n 个岛。其中第一个、最后一个岛以及中间的第 k k k 个岛 ( 1 < k < n ) (1< k< n) (1<k<n) 是固定的,其余岛是悬浮的。一开始所有小球都在第一个岛,你的目标是把他们都移动到最后一个岛,并且使用的步数尽量小。
每回合你可以移动一个小球到它左边或者右边的那个平台,但是有一些限制:
有 q q q 个询问,每次给出一个时刻,你需要回答在最优方案中,这个时刻正在跳跃的是哪个小红球。
保证询问给出的时刻小于等于最优方案数,且保证答案小于等于 30 30 30。
数据范围: n ≤ 1 0 8 n≤10^8 n≤108, m ≤ 1000 m≤1000 m≤1000, q ≤ 5000 q≤5000 q≤5000。
这应该就是汉诺塔问题的变种。
如果有 i i i 个点,那么最优方案为 f i = 3 f i − 1 + ( n − 1 ) f_i=3f_{i-1}+(n-1) fi=3fi−1+(n−1)。而 m ≤ 1000 m\le 1000 m≤1000,貌似直接高精是不行的。
考虑打个表找规律(或者自己手推),如下:
(n-1) 个 1
(k-1) 个 2
(n-1) 个 1
(n-k) 个 2
(n-1) 个 1
(k-1) 个 3
(n-1) 个 1
(n-k) 个 2
(n-1) 个 1
(k-1) 个 2
(n-1) 个 1
(n-k) 个 3
...
如果再打多点,会发现:
由于 2 × 3 30 2\times 3^{30} 2×330 大约是 1 0 14 10^{14} 1014 左右,是在 long long 范围的,可以预处理一下。
然后就可以用高精做了,时间复杂度 O ( q × l e n × 30 ) O(q\times len\times 30) O(q×len×30)。
#include
#define ll long long
using namespace std;
int n,m,k,q;
struct Bignum{
int n,num[505];
Bignum() {n=0,memset(num,0,sizeof(num));}
friend Bignum operator+(const Bignum &A,const Bignum &B){
Bignum C;C.n=max(A.n,B.n);
for(int i=1;i<=C.n;++i){
C.num[i]+=A.num[i]+B.num[i];
if(C.num[i]>9) C.num[i]-=10,C.num[i+1]++;
}
if(C.num[C.n+1]) C.n++;
return C;
}
friend Bignum operator/(const Bignum &A,ll b){
ll ans=0;Bignum C;C.n=A.n;
for(int i=C.n;i;--i){
ans=ans*10+A.num[i];
if(ans>=b) C.num[i]=ans/b,ans=ans-(ans/b)*b;
}
while(C.n&&!C.num[C.n]) C.n--;
return C;
}
friend ll operator%(const Bignum &A,ll b){
ll ans=0;
for(int i=A.n;i;--i){
ans=ans*10+A.num[i];
if(ans>=b) ans=ans-(ans/b)*b;
}
return ans;
}
void Read(){
char c=getchar();n=0;
while(!isdigit(c)) c=getchar();
while( isdigit(c)) num[++n]=c^48,c=getchar();
reverse(num+1,num+n+1);
}
}One,p;
ll Mod,f[35];
void init(){
f[1]=1,f[2]=2;
for(int i=3;i<=31;++i) f[i]=3ll*f[i-1];
One.n=1,One.num[1]=1;
}
void adjust(){
if(Mod>0) p=p+One;
if(Mod>n-1) p=p+One;
if(Mod>n-1+k-1) p=p+One;
if(Mod>n-1+k-1+n-1) p=p+One;
}
int main(){
init();
scanf("%d%d%d%d",&n,&m,&k,&q);
int tmp=3*n-3; //(n-1)+(k-1)+(n-1)+(n-k)
while(q--){
p.Read(),Mod=p%tmp;
p=p/tmp,p=p+p,p=p+p;
adjust();
for(int i=1;i<=30;++i)
if(p%f[i]==0&&p%f[i+1]!=0) {printf("%d\n",i);break;}
}
return 0;
}