题解
使用操作
1
1
1只是为了给操作
2
2
2或操作
3
3
3凑整,因此问题变为递归考虑在进行了
x
1
x1
x1次操作
1
1
1后再进行
x
2
x2
x2次操作
2
2
2或者操作
3
3
3使得
(
x
1
+
x
2
)
(x1+x2)
(x1+x2)有最小值即可。
于是按照这个思路对问题进行分治处理,设
f
n
f_n
fn为将
n
n
n操作到
0
0
0所需要的最小次数,可以得到如下式子:
f
n
=
m
i
n
{
f
n
,
f
(
n
/
2
)
+
(
n
%
2
)
,
f
(
n
/
3
)
+
(
n
%
3
)
}
f_n=min\{f_n,f_{(n/2)}+(n\%2),f_{(n/3)}+(n\%3)\}
fn=min{fn,f(n/2)+(n%2),f(n/3)+(n%3)}
其中取模操作就相当于操作
1
1
1,
/
2
/2
/2或
/
3
/3
/3就相当于进行操作
1
1
1后的操作
2
2
2或操作
3
3
3,然后一起取个最小值即可。可以使用
S
T
L
STL
STL中
m
a
p
map
map进行记忆化。
初始化
f
1
=
1
,
f
0
=
0
f_1=1,f_0=0
f1=1,f0=0
注意当
n
=
=
2
n==2
n==2或
n
=
=
3
n==3
n==3时,次数为
2
2
2,但是代入上式因为
%
2
o
r
%
3
=
=
0
\%2 or\%3==0
%2or%3==0,所以我们需要在判断时加一句
(
n
≥
2
)
(n\ge2)
(n≥2)和
(
n
≥
3
)
(n\ge 3)
(n≥3)处理一下即可。
考场
当时看到
n
≤
1
e
9
n\le 1e9
n≤1e9的范围时想到了对其要分治递归处理,但是因为题做的不够所以直接考虑的用自己瞎想还想错了的最优搜索顺序进行搜索后得答案而没有分开搜索取
m
i
n
min
min。考场上想的是先搜
3
3
3比先搜
2
2
2更优而没有考虑这样搜索之后后面的值不一定更优,所以要多手玩数据找规律,发现操作
1
1
1其实就相当于取模为操作
2
,
3
2,3
2,3做准备后再联想到对每条搜索路径分治
+
+
+记忆化之后思路就很显然了。
其实这道题正解和暴力思路很像,只不过暴力是用
d
p
dp
dp预处理
n
≤
1
e
7
n\le 1e7
n≤1e7,相当于复杂度就是
O
(
n
)
O(n)
O(n),而考虑缩小暴力复杂度就是对
n
n
n进行操作,实际式子和暴力其实没什么区别。
总结
考场上一定要先想暴力,因为暴力会给正解一定的思路,就像这道题的式子是一样的,再考虑暴力复杂度主要是哪里过不去,那么就在正解里对这个复杂度进行处理,就像这道题对 n n n进行分治,那么正解也就呼之欲出了。
代码
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N=1000050;
const int M=35;
int T,n,f[N];
int ans=1;
map<int,int> mp;
int dp(int n){
if(n==0) return 0;
if(n==1) return 1;
if(mp.count(n)) return mp[n];
int ans1=dp(n/2)+n%2+(n>=2);
int ans2=dp(n/3)+n%3+(n>=3);
mp[n]=min(ans1,ans2);
return mp[n];
}
inline int read(){
int cnt=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
while(isdigit(c)){cnt=(cnt<<1)+(cnt<<3)+(c^48);c=getchar();}
return cnt*f;
}
signed main(){
freopen("magic.in","r",stdin);
freopen("magic.out","w",stdout);
ios::sync_with_stdio(false);
cout.tie(0);
T=read();
while(T--){
n=read();
cout<<dp(n)<<endl;
}
return 0;
}