https://ac.nowcoder.com/acm/problem/20203

大佬原帖:

https://www.cnblogs.com/acceptedzhs/p/solution-p5261.html

%%%% tql 写了点注释

#include <iostream>
#include <bitset>
#include <algorithm>
#include <vector>
#define int long long

using namespace std;

const int N = 110, S = 1010;

int k, s, p, d;
bitset<S> f[N][S][11];

void Init()
{
	cin >> k >> s >> p >> d;
	f[0][0][0][0] = 1; // 定义起始状态合法
	
	// f[i][s][x][p-x], i+1<=k 
	for (int i=0;i < k;i ++ )
	{
		for (int ss=0;ss <= i*9;ss ++ )		// 枚举原数数位和
		{ 
			for (int x=0;x <= 9;x ++ ) 		// 枚举进位 x 
			{
				for (int j=(i==k-1);j <= 9;j ++ ) 	// 枚举放的数 j 
				{
				//	if (!j && i+1 == k) continue ;	// 前导零
					
					/*
						f[i+1][s+j][sum/10][p + sum%10] += f[i][s][x][p];
						最高位以前的0没有意义,所以可以用 bitset 直接异或
						如果不是?那么右移后多出长度<S>的部分会被看做零,这题没有意义,所以可以用bitset优化层 
                    	跳过枚举 p
						假设 sum%10  =3
						对于每一个 p∈[3, S]: f[][][][p] += f[][][][p - 3] 
						(题目给定后数为数不会超过 1000) 
						形如:(左边低位 
						f[p]	0111 0010 0110 0101 1000
						f[p-3]:	___1 0010 0110 0101 1000 000_ 是合法的 
						即 (0111 0010 0110 0101 1000) |= (1001 0011 0010 1100 0000)
							
						只需要考虑是否大于0即可,所以可以直接异或
					*/ 
					
					int sum = x + j * d;
					f[i+1][ss+j][sum/10] |= (f[i][ss][x] << (sum%10));
				}
			}
		}
	}
}

signed main()
{
	cin.tie(0) -> sync_with_stdio(0);

	/*
		f[i][s][x][p] 
    	从低到高第几位、原数的各数位和、原数乘D后第一位是什么、原数乘D后除了第一位后面数位的各数位和
		所以有 S = s, P = x+p
		
		枚举第i+1位的取值j,转移可以表示为
			f[i+1][s+j][(x+j*d)/10][p + (x+j*d)%10] += f[i][s][x][p]
		
		i+1, s+j 没什么好说的
		
		(x+j*d)/10 : 因为x是"原数*d"后,第i位的进位,
		假设原数为 [????] 那么原数*d可以表示为 x[????]
		考虑上第i+1位上放了j, 那么原数现在为 j[????],乘上d就为 (j*d+x)[????]
		(j*d+x)可能为一位数,也可以为两位数,所以新的最高位的进位就是 放了j后又乘上d 最高位的进位
		即 (j*d+x)/10, 注意是除10向下取整不是模10, 假设 514 * 9 = 4626 最高位的进位是 (5*9=45)/10 = 4
		
		p+(x+j*d)%10 : 首先明确,p表示原数乘上d后,除了最高位(进位)后面各数位的和 
		记 DS(X) 表示X各数位的和,所以我们有 P = DS(num*d) = x + p, (没放j之前
		考虑第i+1放了j, 那么现在数再乘上d,除了最高位,后面的数位和可以计算
		(j*d + x)[????] -> (j*d+x)/10[(j*d+x)%10????] (这个数长成这样)
		所以就是 p + (j*d+x)%10
		 	
	*/

	Init();
	
	vector<int> ans(N); // 最多100位
	
	bool is = 0;
	for (int x=0;x <= 9 && !is;x ++ )
	{
		if (p-x >= 0 && f[k][s][x][p-x])
		{
			is = 1, p -= x;
			// "直接" 说明x合法且有解 
			// 第一次找到,则一定由解,则一定最小
			// 为什么一定有解?因为递推是从小到大递推,
			// 如果终点以后解,那么一定可以枚举出路径
			// 除了j,枚举这里就不需要考虑枚举是从小到大还是从大到小了 
			// 因为终点已经确定是最小了 
			
			
			// 从高到低, 从小到大枚举放的数 j 
			for (int i=k;i >= 1;i -- )
			{
				for (int j=(i==k);j <= 9;j ++ )
				{
					/*
						贪心策略
						从高位到低位,放的j从小到大,枚举
						最高位肯定不能是零 否则其他可以是0 
					*/
					
				//	if (!j && i == k) continue ; // 前导零
					
					for (int rx=0;rx <= 9;rx ++ )
					{
						int sum = rx + j*d, nowSum = s - j;
						
						if (nowSum < 0 || sum/10 != x) continue ;
						/*
							因为 f[i+1][s+j][sum/10][p + sum%10] += f[i][s][x][p];
							nowP = p - sum%10
						*/
						int nowP = p - sum%10;
						// f[i+1][s+j][sum/10][p + sum%10] += f[i][s][x][p]
						if (nowP < 0 || !f[i-1][nowSum][rx][nowP]) continue ;
			
						// 找到一个合法的路径 
                        // 因为推出 f[i-1] 合法,说明一定有起点
						// 不需要像递归搜索那样需要反悔 
						ans[i] = j, x = rx, s = nowSum, p = nowP; // 根据路径递推回去 
						
						goto anchor;
					}
				}
				
				anchor: ;
			}
		}
	}
	
	
	if (!is) return cout<<-1, 0;
	
	for (int i=k; i;i -- ) putchar(ans[i] + '0'); 
	
	return 0;
}