Better_Dice题目转跳

写在前头

因为太久没有写过题解了,包括自己也是刚开始复健训练,所以题解内容如果有误,欢迎来指正,如果有不懂的地方也欢迎来问。祝愿各位未来在信息学的道路上RP++

First 简化题意

先简单的抽象下题面

给你一个六面骰,并告诉你骰子的每个面,然后要求你构造一个骰子面数总和为k的六面骰,使得每个骰子各骰一次的情况下,后者的胜率严格大于前者,即后者最大的19个情况全都比前者最小的19个情况大。

最后,如果可以构造出来该骰子,输出YES和你的构造结果,如果不行,输出NO

Second 数据范围观察的必要

对于一个题目而言,第一重要的是题面,而第二重要的就是数据范围。

注意到,1 <= k <= 10^18

我们知道int的取值范围在-2^32<= x <= 2^32

而232=4294967295,即109的范围,如果用int的话这道题从输入开始就会出现问题了,所以至少要用long long.

其次,观察数据范围也是一个帮我们考虑如何选择算法的方式。

这道题这么大的数据量就可以很轻松地pass掉直接枚举六个面大小来计算的情况,因为该情况的时间复杂度为O(k^6)很容易被卡掉。

那么根据k来看,我们考虑的复杂度至少是在O(log k)起步的,或者对于大部分的这种情况的大数据来说,O(1)的时间复杂度是一种更可能的情况

总之,想到这里,我们已经对这道题有着些初步的思考了。

Third 理解样例

这个原题有写,在此就不多赘述了,只是简单地拿出来举例一下写题的大致步骤,而且对于多种情况的构造题而言,给的样例大概率是用来理解题面意思的,有时候靠自己构造出一种成功的情况来可能更适合启迪自己

Fourth 大胆猜测

当你读完题时,你可能会发现一件很有意思的事情,那就是投掷的可能性是6*6,而结果是他们的两两组合。

也就是对于一组数据a,新骰子值为x

如果存在

x > a

那么对于这一次来说一定是胜利这不是废话吗

这样,我们就想到了一种神奇的构造想法——分配胜利数

新骰子的每个面对应老骰子的每个面的胜利数刚好为0~6

如果他们的总和大于等于19,那么这个就是一种成立的构造情况

Fifth 合理构造

那么,竟然我们想到来为每面骰子枚举胜利数这个想法,对于k这个有限的值我们也必须物尽其用,我们规定构造的以下四点规则

1.如果该数的胜利数为 x ,那么这个数的大小应该为 老骰子中第 x 大的数+1

2.如果该数的胜利数为 0 , 那么为了节约 k 值,我们将这个数的大小设定为 1

3.对于剩下的 $k$ ,直接随便分配就行,因为无论怎么分配都是一个合法的情况

4.如果该法无法成立,那么这将是一个无法构造出来的骰子。

这样,我们相当于是把所有情况都穷举了一遍,如果这样都无法构造出来一个骰子,那么很遗憾,这是一个不可能的壮举()。

The End 参考代码

我觉得对于新手来说参考代码还是有很大必要的,把原来的代码修修改改了一番,现在应该是一个码风稍微比较适合新的阅读的情况了(存疑)

#include<bits/stdc++.h>
using namespace std;
//定义long long 防止数值溢出
long long k;
long long a[7];//本程序的骰子的面数从1开始枚举下标到6,所以数组大小定义应该为7
long long b[7];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    for(int i=1;i<=6;i++)//循环6次,读入老骰子所有的面数
    {
        cin>>a[i];
    }
    cin>>k;
    sort(a+1,a+7);//C++自带的快速排序函数,强烈建议学习,不会的可以手写冒泡之类的排序代替
    for(int w1=0;w1<=6;w1++)//枚举每面的胜利数,惊人的六重循环
    {
        for(int w2=0;w2<=6;w2++)
        {
            for(int w3=0;w3<=6;w3++)
            {
                for(int w4=0;w4<=6;w4++)
                {
                    for(int w5=0;w5<=6;w5++)
                    {
                        for(int w6=0;w6<=6;w6++)
                        {
                            long long tot=w1+w2+w3+w4+w5+w6;//统计胜利数的总数
                            if(tot>=19)//如果是一种成立的情况
                            {
                                long long k_change=k;//构造的第三种情况会用到
                                b[1]=a[w1]+1;//构造的第一种情况
                                b[2]=a[w2]+1;
                                b[3]=a[w3]+1;
                                b[4]=a[w4]+1;
                                b[5]=a[w5]+1;
                                b[6]=a[w6]+1;
                                k_change=k_change-b[1]-b[2]-b[3]-b[4]-b[5]-b[6];//计算剩下的k值
                                if(k_change>=0)//可以构造出来
                                {
                                    cout<<"YES"<<'\n';
                                    cout<<b[1]+k_change;//构造的第三种情况以及防止多余空格的提前输出
                                    for(int i=2;i<=6;i++)
                                    {
                                        cout<<" "<<b[i];//输出
                                    }
                                    return 0;
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    cout<<"NO";//穷举完了仍然不成立的情况
    return 0;
}

最后的最后

不知道写啥了先空着,未来有机会补。