暑假集训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;
}
posted @ 2023-08-12 19:47  LZH_03  阅读(21)  评论(0编辑  收藏  举报