Luogu P1874 快速求和 (线性DP)
快速求和
题目描述
给定一个数字字符串,用最小次数的加法让字符串等于一个给定的目标数字。每次加法就是在字符串的某个位置插入一个加号。在里面要的所有加号都插入后,就像做普通加法那样来求值。
例如,考虑字符串12
,做 0 0 0 次加法,我们得到数字 12 12 12。如果插入 1 1 1 个加号,我们得到 3 3 3,因此,这个例子中,最少用 1 1 1 次加法就得到数字 3 3 3。
再举一例,考虑字符串303
和目标数字 6 6 6,最佳方法不是3+0+3
。而是3+03
。能这样做是因为一个数的前导 0 0 0 不会改变它的大小。
输入格式
第一行:一个字符串 s s s。
第二行:一个整数 n n n。
输出格式
一行一个整数表示最少的加法次数让 s s s 等于 n n n。如果怎么做都不能让 s s s 等于 n n n ,则输出 − 1 -1 −1。
样例 #1
样例输入 #1
99999
45
样例输出 #1
4
提示
数据规模与约定
对于 100 % 100\% 100% 的数据,保证 1 ≤ len ( s ) ≤ 40 1\le \operatorname{len}(s)\le40 1≤len(s)≤40, 1 ≤ n ≤ 1 0 5 1 \leq n\le10^5 1≤n≤105。
没经验的话看不出来是一道dp题,感觉很像搜索题。
DP数组的状态表示:使用dp[i][j]
来表示结尾为 i i i,并且和为 j j j的加入加号最小的方案。
需要先处理出来二维数组来表示能够得到的所有加数,即用num[i][j]
表示在 i i i前面加上加号之后,我们从 i i i到 j j j通过字符串得到的加数。
那么dp[i][j]
可以由什么状态转移过来?
在这里能够选择的操作就是加上加号或者不加。
在不加的时候,则状态就是dp[i][j]
自己。在加上加号转移过来的时候,那么状态就是dp[k][j - num[k+1][i]] + 1
这里解释一下, k k k代表结尾为 k k k,j - num[k+1][i]
代表 j j j在加上加数num[k+1][i]
之前的状态,也就是说我们是在 k k k后面加上了加号。
同时注意如果当前枚举到的加数的大小已经超过目标大小的时候可以直接跳出当前层循环。
int num[45][N];
int f[45][N];void solve(int times)
{string s;int n;cin >> s>>n;for(int i = 1;i <= s.size();i++){for(int j = i;j <= s.size();j++){num[i][j] = num[i][j-1]*10 + (s[j - 1] - '0');}}memset(f,0x3f,sizeof f);f[0][0] = -1;for(int i = 1;i <= s.size();i++){for(int k = 0;k <= n;k++){for(int j = i - 1;j >= 0;j--){if(num[j + 1][i] > n)break;if(k - num[j+1][i] < 0)continue;f[i][k] = min(f[i][k],f[j][k - num[j+1][i]] + 1);}}}if(f[s.size()][n] > 40)cout << -1 << endl;else cout << f[s.size()][n] << endl;
}