Acwing春季每日一题 混合牛奶(朴素做法和优化做法)

感 谢 大 佬 们 的 点 赞   O r z \color{purple}{感谢大佬们的点赞~Orz}  Orz

题目描述

农业,尤其是生产牛奶,是一个竞争激烈的行业。

Farmer John 发现如果他不在牛奶生产工艺上有所创新,他的乳制品生意可能就会受到重创!

幸运的是,Farmer John 想出了一个好主意。

他的三头获奖的乳牛,Bessie、Elsie 和 Mildred,各自产奶的口味有些许不同,他打算混合这三种牛奶调制出完美的口味。

为了混合这三种不同的牛奶,他拿来三个桶,其中分别装有三头奶牛所产的奶。

这些桶可能有不同的容积,也可能并没有完全装满。

然后他将桶 1 1 1 的牛奶倒入桶 2 2 2,然后将桶 2 2 2 中的牛奶倒入桶 3 3 3,然后将桶 3 3 3 中的牛奶倒入桶 1 1 1,然后再将桶 1 1 1 的牛奶倒入桶 2 2 2,如此周期性地操作,共计进行 100 100 100 次(所以第 100 100 100 次操作会是桶 $1 $倒入桶 2 2 2)。

当 Farmer John 将桶 a a a 中的牛奶倒入桶 b b b 时,他会倒出尽可能多的牛奶,直到桶 a a a 被倒空或是桶 b b b 被倒满。

请告诉 Farmer John 当他倒了 100 100 100 次之后每个桶里将会有多少牛奶。

输入格式

输入文件的第一行包含两个空格分隔的整数:第一个桶的容积 c 1 c_1 c1,以及第一个桶里的牛奶量 m 1 m_1 m1

第二和第三行类似地包含第二和第三个桶的容积和牛奶量。

输出格式

输出三行,给出倒了 100 100 100 次之后每个桶里的牛奶量。

数据范围

1 ≤ c 1 , m 1 ≤ 1 0 9 \color{Red}{1≤c_1,m_1≤10^9} 1c1,m1109

输入样例
10 3
11 4
12 5
输出样例
0
10
2
样例解释

在这个例子中,每倒一次之后每个桶里的牛奶量如下:

初始状态:   3  4  5
1. 桶1->2:  0  7  5
2. 桶2->3:  0  0  12
3. 桶3->1:  10 0  2
4. 桶1->2:  0  10 2
5. 桶2->3:  0  0  12
(之后最后三个状态循环出现……)

算法1

(朴素版暴力迭代模拟) O ( 1 ) \color{Red}{O(1)} O(1)
  • 读入3个桶的容积和牛奶量

  • 从第一个桶开始idx = 1,则下一个桶next = (idx + 1) % 4 ? idx + 1 : 1
    无标题.png

  • 设下一个桶为 n e x t next next,则 n e x t next next桶的容积为 n e x t ( v ) next(v) next(v), n e x t next next桶的牛奶量为 n e x t ( w ) next(w) next(w),则有
    1111.png
    2222.png

时间复杂度: O ( 1 ) O(1) O(1)
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

typedef pair<int, int> PII;//容积,牛奶量

PII a[4];

int main()
{
    for (int i = 1; i <= 3 ; i++ )//读入3个桶的容积和牛奶量
    {
        int x, b;
        scanf("%d%d", &x, &b);
        a[i] = {x, b};
    }
    
    int idx = 1;//记录一下从第一个桶开始
    for (int i = 0; i < 100; i ++ )//迭代100次
    {
        int next = (idx + 1) % 4 ? idx + 1 : 1;//应该往哪个桶倒idx->next
        int v = a[next].first;//查看一下next桶的容积
        int w = a[next].second;//查看一下next桶的牛奶量
        if (v == w)//如果next桶的容积等于牛奶量了,说明不需要再倒牛奶了~
        {
            
        }
        else if (a[idx].second + w <= v)//如果idx桶的牛奶量与next桶的牛奶量要小于next桶的容积
        {
            a[next].second = a[idx].second + w;//更新next桶的牛奶量
            a[idx].second = 0;//idx桶的牛奶量为0
        }
        else //如果idx桶的牛奶量与next桶的牛奶量要大于next桶的容积
        {
            a[idx].second -= v - w;//更新idx桶的剩余的牛奶量
            a[next].second = v;//next桶的牛奶量则为其容积
        }
        idx = next;//idx桶指向下一个桶(next)
    }
    
    for (int i = 1; i <= 3; i ++ )//输出迭代100次的答案~
        printf("%d\n", a[i].second);
    
    return 0;
}

算法2

(找环进行优化操作)

由我们的样例解释可知

初始状态:   3  4  5
1. 桶1->2:  0  7  5
2. 桶2->3:  0  0  12
3. 桶3->1:  10 0  2
4. 桶1->2:  0  10 2
5. 桶2->3:  0  0  12
(之后最后三个状态循环出现……)

可以找到一个 环 \color{Red}{环} ,使得只用计算后续 经 过 多 少 个 环 \color{Red}{经过多少个环} 再 经 过 多 少 步 得 到 的 最 终 状 态 \color{Red}{再经过多少步得到的最终状态} ~
如下图所示:
阿斯顿撒旦.png

我们只需要用一个字符串记录三个桶牛奶量的状态,利用map看之前是否出现过,出现过的话,我们就找到了一个环直接退出循环,计算经过``p``圈后还需走多少步达到100次,最终输出答案即可
这样我们就不用枚举100次了~

士大夫士大夫胜多负少.png

C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_map>

using namespace std;

typedef pair<int, int> PII;//容积,牛奶量

PII a[4];
unordered_map<string, int> state;
int r;
string s;

int main()
{
    for (int i = 1; i <= 3 ; i++ )
    {
        int x, b;
        scanf("%d%d", &x, &b);
        a[i] = {x, b};
    }
    
    int idx = 1;
    for (int i = 1; i <= 100; i ++ )
    {
        int next = (idx + 1) % 4 ? idx + 1 : 1;
        int v = a[next].first;
        int w = a[next].second;
        if (v == w)
        {
            
        }
        else if (a[idx].second + w <= v)
        {
            a[next].second = a[idx].second + w;
            a[idx].second = 0;
        }
        else 
        {
            a[idx].second -= v - w;
            a[next].second = v;
        }
        idx = next;
        
        /*上方的注释请看朴素版的算法~*/
        
        //合成一下状态(桶1的牛奶量, 桶2的牛奶量, 桶3的牛奶量)
        s = to_string(a[1].second) + " " +  to_string(a[2].second) + " " + to_string(a[3].second);
        
        if (!state.count(s))//如果该状态在之前没有,则插入该状态
            state[s] = i;
        else //若有,则找到环的大小,并退出循环
        {
            r = (100 - i) % (i - state[s]);
            break;
        }
    }
    
    int x = state[s] + r ? state[s] + r : 1;//找到最终状态的位置~

    for (auto t : state)//找到最终状态`
        if (t.second == x)
        {
            s = t.first;
            break;
        }
    
    //处理一下输出结果~   
    int t = s.find(' ');
    for (int i = 0; i < t; i ++ ) printf("%c", s[i]);
    s[t] = '.';
    puts("");
    
    int p = s.find(' ');
    for (int i = t + 1; i <= p; i ++ ) printf("%c", s[i]);
    s[p] = '.';
    puts("");
    
    int len = s.length();
    for (int i = p + 1; i < len; i ++ ) printf("%c", s[i]);
    puts("");
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Nie同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值