【题解】CF509C Sums of Digits

一道很好的构造题。简单来说,给你一个序列 {b}\{b\}{b}bib_ibi 表示 aia_iai 各位数字之和,请还原出序列 {a}\{a\}{a}。有两个附加条件,一是序列 {a}\{a\}{a} 单增,二是 {a}\{a\}{a} 尽可能的小。

对于第二个条件,很容易想到要贪心求解。倘若没有第一个条件,那么显然 bi=9a+b(0<b<9)b_i = 9a+b(0<b<9)bi=9a+b(0<b<9) 的形式是最优的,即还原出的数形如 b99⋯9b99\cdots9b999。那么有第一个条件时,不难想到根据 bi−1b_{i - 1}bi1bib_ibi 的大小进行分类讨论。

  • 先从 bi−1<bib_{i - 1} < b_ibi1<bi 这种简单的情况看起。相较于前一个数,只需要把多的部分 bi−bi−1b_i - b_{i - 1}bibi1 按照贪心的思想去填入即可。举个栗子,如果 ai−1=129a_{i - 1} = 129ai1=129,多出了 888 需要填,那么在 222 上加上 777 补成最大的上限 999,然后将剩余的 8−7=18 - 7 = 187=1 填入 111,最后变成了 299299299

  • 再来看 bi−1≥bib_{i - 1} \ge b_ibi1bi 的情况。从低位往高位找到第一个不是 999 的位置,尝试将该位加 111,往高位的方向均不变,往低位的方向重新排列。这里需要注意是否合法(也就是剩余数字之和在减去那些已经确定的数后是否大于等于 000)。当然,往低位的方向的排列方式直接按照贪心的思路即可。若位数不够,则需要补 000,这也是为什么之前判断合法时只需要满足大于等于 000 即可。如果全部遍历完也未出现可以填的,那么只能新开一位并填上 111,然后剩下的按照贪心的思路即可。

在编写代码的时候,分类三种构造的函数。第一种是无条件限制,直接贪心填入。第二种是弱依赖,即需要补充多的部分的第一种情况。第三种便是需要较大改动的第二种情况,其中按照贪心进行填入的部分直接调用第一个函数即可。

完整代码如下:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#define init(x) memset (x,0,sizeof (x))
#define ll long long
#define ull unsigned long long
#define INF 0x3f3f3f3f
using namespace std;
const int MAX = 1e5 + 5;
const int MOD = 1e9 + 7;
inline int read ();
int n,a[MAX];
string ans[MAX];
string easy_make (int x);
string medium_make (string la,int x);
string hard_make (string la,int x);
int main ()
{
	//freopen (".in","r",stdin);
	//freopen (".out","w",stdout);
	n = read ();
	for (int i = 1;i <= n;++i) a[i] = read ();
	for (int i = 1;i <= n;++i)
	{
		if (i == 1) ans[i] = easy_make (a[i]);
		else if (a[i] > a[i - 1]) ans[i] = medium_make (ans[i - 1],a[i]);
		else ans[i] = hard_make (ans[i - 1],a[i]);
	}
	for (int i = 1;i <= n;++i) cout<<ans[i]<<endl;
	return 0;
}
inline int read ()
{
    int s = 0;int f = 1;
    char ch = getchar ();
    while ((ch < '0' || ch > '9') && ch != EOF)
	{
        if (ch == '-') f = -1;
        ch = getchar ();
    }
    while (ch >= '0' && ch <= '9')
	{
        s = s * 10 + ch - '0';
        ch = getchar ();
    }
    return s * f;
}
string easy_make (int x)//直接贪心填入
{
	string s = "";
	for (int i = 1;i <= x / 9;++i) s += '9';
	x %= 9;
	if (x) s = char (x + 48) + s;
	return s;
}
string medium_make (string la,int x)
{
	int len = la.size ();int sum = x;
	string s = "";
	for (int i = 0;i < len;++i) sum -= la[i] - '0';
	for (int i = len - 1;~i;--i)
	{
		if (!sum) s = la[i] + s;
		else s = char (la[i] + min (9 - (la[i] - '0'),sum)) + s,sum -= min (9 - (la[i] - '0'),sum);//尽量凑成 9
	}
	return easy_make (sum) + s;
}
string hard_make (string la,int x)
{
	int len = la.size ();
	for (int i = len - 1;~i;--i)
	{
		if (la[i] != '9')
		{
			string s = "";int sum = 0;
			for (int j = 0;j < i;++j) s += la[j],sum += la[j] - '0';
			s += char (la[i] + 1);sum += la[i] - '0' + 1;
			if (x - sum < 0) continue;//要合法才行
			string nw = easy_make (x - sum);
			int p = len - s.size () - nw.size ();
			for (int j = 1;j <= p;++j) s += '0';//补 0 保证 ans_i 单调递增
			return s + nw; 
		}
	}
	string s = "1";++len;//直接新开一位
	string nw = easy_make (x - 1);
	int p = len - s.size () - nw.size ();
	for (int i = 1;i <= p;++i) s += '0';//补 0 保证 ans_i 单调递增 
	return s + nw; 
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值