[笔记] P1874 快速求和

P1874 快速求和

下文中,我们用 \(n\) 表示 \(s\) 的长度, \(x\) 表示要拼出的数。

使用线性DP,设 \(f[i,j]\) 表示在 \(s[1...i]\) 中插入加号,使得算出的结果为 \(j\) 的加号最少个数。

对于这种类型的dp,有一个非常经典的转移方法:枚举最后一个加号的位置。

设最后一个加号添在 \(s[k]\) 之前,有转移: \(f[i,j]=\min\{f[k-1,j-num(k,i)+[k!=1]\} \, \, (num(k,i)<j)\) ,其中 \(num(l,r)\) 表示 \(s[l...r]\) 对应的十进制数。

为什么转移方程不是 $f[k-1,j-num(k,i)]+1$

\(k=1\) 时,在 \(k\) 前面添加加号没有意义,此时不应该 \(+1\) .

为了降低时间复杂度,枚举 \(k\) 时应倒序枚举,当枚举到第一个使得 \(num(k,i)>j\)\(k\) 时就没有必要继续枚举了,直接退出循环即可。

#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=50, M=1e5+10, INF=1e12;
const int p[]={1,10,100,1000,10000,(int)1e5, (int)1e6, (int)1e7, (int)1e8, (int)1e9};
int f[N][M];
string s;
int n,m,ans;

signed main(){
	cin.tie(0)->sync_with_stdio(0);
	cin>>s; s=" "+s; cin>>m; n=(int)s.size()-1;
	for(int i=0; i<=n; i++)
		for(int j=0; j<=m; j++) f[i][j]=INF;
	f[0][0]=0;// f[k-1,j-now]可能调用到f[0,0],需要赋初值:前0个字符要凑成0不需要任何加号
    // 其他的f[0][i],f[i][0](i!=0)则不能赋值为0,因为找不到用加号凑出来的方法,所以赋值为INF。
	for(int i=1; i<=n; i++)
		for(int j=1; j<=m; j++){
			int now=0;
			for(int k=i; k; k--){
				now=(s[k]-'0')*p[i-k]+now;// 计算对应的十进制数
				if(now>j) break;// 跳出循环
				f[i][j]=min(f[i][j], f[k-1][j-now]+ (k==1?0:1)); //转移
			}
		}
	if(f[n][m]!=INF) cout<<f[n][m];
	else cout<<-1;
	return 0;
}
posted @ 2026-05-09 19:28  Cute_lxy  阅读(6)  评论(0)    收藏  举报