yzy的欢乐膜你赛后,一份迟来的个人(蒟蒻)思路解析

本文是对2019年yzy举办的原题欢乐赛的个人解题思路解析,涉及积木大赛、转圈游戏和货币系统三个题目。在积木大赛中,通过构造单调栈解决;转圈游戏中利用数学性质解答;货币系统问题通过动态规划降低上界求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景

2019年8月17日上午,yzy开启了懒人出题模式,办起了一场全是原题的 欢乐赛。
而看似稳如老狗的我,在考场上是慌的一批,乱搞满分之后更加感觉我思路的清(混)奇(乱),意图将思路整合梳理,故有此文。

T1:积木大赛

传送门
来自NOIp2013/2018提高组以及USACO13MAR_Poker Hands。(这三道题居然一毛一样)
虽说我在很久之前做过原题,但是在考场上我还是经过了一番思(乱)考(搞)。
对于题目要求的形状,我先考虑一种脑残情况:
在这里插入图片描述
根据明显的贪心原则,正常人一定会选择最大的宽度堆积木,这样可以使得总次数最小,为高度h(相当容易证明)。
考场上的我仔细分析了一波这种情况,发现其过于脑残以至于没有任何有价值的信息,我开始陷入绝望。

如果情况并不是这么简单呢?像这样
在这里插入图片描述
根据贪心原则,可以看出最小操作数依然为h,只不过选择的最大宽度会改变而已。
从这种情况里,还可以得到一个有用的信息:在矩形高度递增的情况下,最小操作数就是最高矩形的高度。
如果利用这种具有单调性的思想,将题目里的目标条件强制转化为一个单调递增(指高度)的矩形序列,那么就可以轻而易举地得到答案。
当时面如死灰的我已经无法思考别的情况,只想用这种性质骗分。
那么如何将一般化的情形转化为单调情形呢,我YY了类似单调栈的思想。
从左到右依次扫描题目给的每个矩形,并且假定当前的矩形序列是单调的。
如果当前遇到的矩形高度大于等于上一个的话,什么也不用做,因为此时矩形序列满足单调性。
当遇到一个矩形,其高度比上一个小,那么把这个矩形抬起来,使其和上一个矩形一样高,来维护这个序列。
就像这样:
在这里插入图片描述
根据题意,进行“抬高矩形”的操作的操作步数显然是(上一个矩形的高度-当前矩形的高度)。
当扫描完成之后,我们自然得到了当前序列的最小操作步数,但是不要忘了累计上用来维护矩形序列花费的步数。
所以我的核心代码是这样子的:

long long int height, cur_height, n, ans = 0;
cin >> n >> height;
while (--n)
{
   cin >> cur_height;
   if (cur_height < height)
      ans += height - cur_height;
   height = cur_height;
}
cout << ans + height << endl;

看了一眼数据范围,这种O(N)复杂度的算法可以无压力跑过。(单调栈从来都是那么给力)

T2:转圈游戏

传送门

就是个SB数学题,只考了个取模意义下运算法则的性质。(貌似还有快速幂?反正我闲的无聊写了一个)

这完全没啥可说的。

T3:货币系统

传送门

这道题让我裂开。
刚开始我看到这道题,什么也想不出来,于是企图利用rand()骗分。
用rand(),就要最小化答案的上下界,因为我们要对随机数取模,从而将其限制在最精确的答案范围内。
我想了想,如果两个货币系统一模一样,那么它们必然等价,也就是说答案的上界是所给货币系统的货币种数。
下界不是那么好想到,我寻思下界选个1总不会错。
于是,srand(time(NULL)), cout<<(rand() % n + 1)<<endl;
然后开始想正解。

我想,如何才可以让上界缩小呢?一定是货币系统中的某一种货币面值可以被合法的某种其他方式表示出来,这时上界就可以减少1
举个例子,有个货币系统,里面给定的面值有三种,是2,3,5。此时我们答案的上界是3。
我发现,5这个面值可以被2+3代替,也就是说所有含有用5表示的价值,里面的5都可以用2+3代替。那么,这个货币系统等价于只含有面值2,3的货币系统,答案的上界-1,变成2。
再举个例子,货币系统含有(3,4,5,17),因为17可以被5*2+3+4表示,答案的上界就从4变成3。

如果遍历并且判断给定货币系统中含有的每一个面值,就能获得最终答案。
我使用value[i]表示面值是否可以被表示,a[i]表示货币系统中第i种面值,那么程序里应该有这样一部分:

ans = n;
for (int i = 1; i <= n; i++)
{
    if (value[a[i]])
    {
        ans--;
    }
    //以及其它的操作...
}

那么现在的问题就是,如何求出value数组。
考场上我经过一番冥思苦想,想到了对于任意价值k,某种货币面值v,如果价值(k-v)可以被其它方式表示,那么k也一定可以被表示。
欸,这不就是个状态转移方程么??大概长得像这样:value[k]=value[k] || value[k-a[i]]。
然后这道题就变成了动态规划。

P.S.正确的说法应该是“对于任意价值k,某种货币面值v以及某个正整数倍数p,如果价值(k-p*v)可以被其它方式表示,那么k也一定可以被表示”,但是由于这道题可以转化为动态规划,所以我们可以只考虑倍数为1的情况。

喜闻乐见的DP和细节处理环节

cin >> n;
for (int i = 1; i <= n; i++)
{
    cin >> a[i];
}
sort(a + 1, a + n + 1);//细节排序,使得我们在DP时获得了枚举上界:a[n]
memset(value, 0, sizeof(value));
value[0] = true;
ans = n;
for (int i = 1; i <= n; i++)
{
    if (value[a[i]])
        ans--;
    else		//为什么要用else?如果某一个面值可以被其它方式表示出来,
    			//那么这个面值可以表示的所有价值必然也可以被其它方式表示,
   			//也就是说这些价值会被其它面值dp到,我们直接跳过就行了
        dp(i);
}
cout << ans << endl;

void dp(int i)		//对第i种面值进行dp
{
    for (int j = a[i]; j <= a[n]; j++)//显然上界是a[n]
    {
        value[j] = value[j] || value[j - a[i]];
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值