AtCoder - ABC 155 - E(思维/DP)

3 篇文章 0 订阅
1 篇文章 0 订阅

 E - Payment(思维)

题意:

在一个国家纸币只有 10^{100}+1 种面额,分别1,10,10^{2},10^{3},…,10^{10^{100}}。你需要去支付n元,可能是你付出了一个更大的钱数,对方再找回你差值的钱。

问整个交易过程最少需要用到多少个纸币?

数据范围:

1 ≤ N ≤ 10^{1,000,000}

思路:

对于给出数的每一位,用nas直接记录每一位所需的钞票数,用res记录进位所需的钞票数,若:1.x<5,直接自己给x即可。
2.x>5,别人给10-x。
3.x=5,前一位满足大于等于5时进位。
不是简单的四舍五入,如:15,如果进位算15->20 -> 5+2=7,实际为1+5=6;65,如果进位算65->70->100 -> 5+3+1=9;直接算是6+5=11。

Code:

#include<iostream>
#include<algorithm>
using namespace std;

//#define int long long

void solve()
{
	string s;
	cin >> s;
	int ans = 0, res = 0;
	s = '0' + s;                   //在s前加一位'0',防止首位进位
	int len = s.size();

	//计算进位需要的钞票数
	for (int i = len - 1; i >= 1; i--)
	{
		if (s[i] > '5')
		{
			res += 10 - (s[i] - '0');
			s[i] = '0';
			s[i - 1]++;
		}
		else if (s[i] == '5' && s[i - 1] >= '5')
		{
			res += 10 - (s[i] - '0');
			s[i] = '0';
			s[i - 1]++;
		}
	}

	//算上每一位需要的钞票数
	for (int i = 0; i < len; i++)
		ans += s[i] - '0';

	cout << ans + res << endl;
}

signed main()
{
	int t = 1;
	//cin >> t;
	while (t--)
	{
		solve();
	}

	return 0;
}

DP思路:

对于要支付的钱,用字符串存,从低位到高位,每一位只有两种选择:
1.支付 x 张 10^{i}
2,支付 1 张 10^{i+1},找回 10-x 张 10^{i} 的货币。 

状态表示:f[0][i] 表示第 i 位用第一种方法的最小票数,f[1][i] 表示第i位用第二种方法的最小票数。(最小票数指的是第 i~len 位所用的票数之和最小)

集合划分:根据第 i+1 位的状态为依据将第 i 位的两种状态各自划分为两类:
f[0][i]:(1)当第 i+1 位的选择是1时,即 f[0][i+1],第 i+1 位产生的票数是支付时的 x 张10^{i},对第 i 位无影响,此时的 f[0][i] 就等于 f[0][i+1] 加上第 i 位的票数x:即 f[0][i+1] + (s[i] - '0');
           (2)当第 i+1 位的选择是2时,即 f[1][i+1],第 i+1 产生的票数时店员找回的 10-x 张,同时还需要第 i 位的1张,这时的 f[1][i+1] 没有算上(因为它包含的仅是第 i+1~len 位的票数和),需要将这一张算到第 i 位 f[0][i] 中:即 f[1][i+1] + (s[i] - '0' + 1) 

f[1][i]:(1)当第 i+1 位的选择是1时,同理,在继承 f[0][i+1] 的基础上再算上第 i 位所需的票数 10-x:即 f[0][i+1] + 10 - (s[i] - '0');
         (2)当第 i+1 位的选择是2时,同理,由于第 i+1 位还产生了1张第 i 位的票,所以第 i 位的票数为 x+1,用到的票数为10-(x+1):即 f[1][i+1] + 10 - (s[i] - '0' + 1)

最后得到的 f[0][0], f[1][0] 分别表示对于第1位选择1和选择2时,第 1~len 位所有票数的最小值。只需取min(f[0][0],f[1][0])即可。

注意预处理 f[0][len] 和 f[1][len]。

之所以循环i到0结束,是因为最后一个数即第1位如果选择2,向第0位借位的话,所借的这一张票只能第0位那里补算上,所以最后需要的是 f[0][0] 与 f[1][0]。

Code:

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;

#define int long long
#define x first
#define y second

//typedef long long LL;
typedef pair<int, int>PII;

const int N = 1e6 + 10;

int f[2][N];
char s[N];

void solve()
{
    s[0] = '0';
    cin >> (s + 1);

    int len = strlen(s + 1);                     //记录位数

    //预处理
    f[0][len] = s[len] - '0';                   
    f[1][len] = 10 - (s[len] - '0');

    //dp
    for (int i = len - 1; i >= 0; i--)
    {
        f[0][i] = min(f[0][i + 1] + (s[i] - '0'), f[1][i + 1] + s[i] - '0' + 1);
        f[1][i] = min(f[0][i + 1] + 10 - (s[i] - '0'), f[1][i + 1] + 10 - (s[i] - '0' + 1));
    }

    cout << min(f[0][0], f[1][0]) << endl;
}

signed main()
{
    int t = 1;
    //cin >> t;

    while (t--)
    {
        solve();
    }

    return 0;
}

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
吐槽:一开始看到大佬的题解是没用dp直接思维感觉还比较好理解,又看到大佬用dp,开始属实是看不懂,看懂后,妙啊,dp一般就是代码更简洁,但思路很重要,细节也是。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值