暑假集训D18 2023.8.12
A.1s For All
给定一个数 \(n\) ,用若干个 \(1\) ,经过加法,乘法,括号运算,经过一系列运算出数 \(n\) ,另外还可以使用拼接,将两个数拼接起来,注意拼接的数必须不含前导 \(0\) .
\(\operatorname{Solution}\)
采用动态规划(递推), \(dp[i]\) 表示 \(i\) 用的 \(1\) 的最少的个数,那么显然有
\(dp[i] = \operatorname{min}(dp[k]+dp[i-k]) ,k=1,2,3\dots\)
\(dp[i] = \operatorname{min}(dp[k]+dp[i/k])\) ,当且仅当 \(k\) 能整除 \(i\) 时.
拼接时只需要暴力枚举 \(i\) 的拼接方法即可.
这道题第一发可能卡评测机了,\(TLE\) ,后面 \(WA\) 了几发,赛后发现是拼接时出错了.错误代码如下.
while(t)
{
if(i/(m[mi-1])%10!=0)
{
// if(i==idx&&dp[i]>dp[t]+dp[rest])
// {
// cout<<t<<" "<<rest<<endl;
// }
dp[i] = min(dp[i],dp[t]+dp[rest]);
}
rest = i%m[mi];
mi++;
t/=10;
}
}
这里应该交换 \(mi++\) 和 \(rest\) 赋值的顺序
\(AC\) 代码如下.
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int dp[N];
int m[10];
signed main()
{
int n;
scanf("%d",&n);
m[0] = 1 ;
for(int i = 1;i<10;i++)
{
m[i] = m[i-1]*10;
}
dp[1] = 1;
// int idx = 99;
for(int i=2;i<=n;i++)
{
dp[i] = i;
for(int k = 1;k <= i /2;k++) //只需要枚举到 i/2,实测大大降低了所耗费时间
{
dp[i] = min(dp[i],dp[k]+dp[i-k]);
}
for(int k = 1; k <= i / k; k++)
{
if(i%k==0)
{
dp[i] = min(dp[i],dp[k]+dp[i/k]);
}
}
int mi = 1;
int t = i;
while(t)
{
int rest = t%10;
t/=10;
if(i/(m[mi-1])%10!=0)
{
dp[i] = min(dp[i],dp[t]+dp[rest]);
}
mi++;
rest = i%m[mi];
t/=10;
}
}
printf("%d",dp[n]);
return 0;
}