2021 January Contest Bronze 题解

T1 Uddered but not Herd

题面描述

一个鲜为人知的事实是,奶牛拥有自己的文字:「牛文」。牛文由 26 个字母 'a' 到 'z' 组成,但是当奶牛说牛文时,可能与我们所熟悉的 'abcdefghijklmnopqrstuvwxyz' 不同,她会按某种特定的顺序排列字母。
为了打发时间,奶牛 Bessie 在反复哼唱牛文字母歌,而 Farmer John 好奇她唱了多少遍。

给定一个小写字母组成的字符串,为 Farmer John 听到 Bessie 唱的字母,计算 Bessie 至少唱了几遍完整的牛文字母歌,使得 Farmer John 能够听到给定的字符串。Farmer John 并不始终注意 Bessie 所唱的内容,所以他可能会漏听 Bessie 唱过的一些字母。给定的字符串仅包含他记得他所听到的字母。

输入格式(从终端/标准输入读入):

输入的第一行包含 26 个小写字母 'a' 到 'z' 的牛文字母表顺序。下一行包含一个小写字母组成的字符串,为 Farmer John 听到 Bessie 唱的字母。字符串的长度不小于 1 且不大于 1000。

输出格式(输出至终端/标准输出):

输出 Bessie 所唱的完整的牛文字母歌的最小次数。

输入样例:

abcdefghijklmnopqrstuvwxyz
mood

输出样例:

3

样例解释

在这个样例中,牛文字母表与日常的字母表的排列一致。
Bessie 至少唱了三遍牛文字母歌。有可能 Bessie 只唱了三遍牛文字母歌,而 Farmer John 听到了以下被标记为大写的字母。
abcdefghijklMnOpqrstuvwxyz
abcdefghijklmnOpqrstuvwxyz
abcDefghijklmnopqrstuvwxyz

测试点性质:

测试点 2-5 中,牛文字母表与日常的字母表相同。
测试点 6-10 没有额外限制。

试图直接用一个公式直接写出此题答案,罢了,根本不会写。

好的,那么这题的思路非常的显然,就是说,如果我现在这一位的字符对于前一位字符来说在字母表的较前位置,这显然是不合法的。同时,须要注意的是,如果现在又连续两位的字符,那么肯定是唱了两遍歌的毋庸置疑。

蒟蒻die码:

#include <bits/stdc++.h>
#define pb push_back
using namespace std;
template <typename T>inline void read(T& t){
    t=0; register char ch=getchar();
    while(!('0'<=ch&&ch<='9')){if(ch=='-') t=-1;ch=getchar();}
    while(('0'<=ch&&ch<='9')){t=((t<<1)+(t<<3))+ch-'0'; ch=getchar();}
}
template <typename T,typename... Args> inline void read(T& t, Args&... args){
    read(t);read(args...);
}
template <typename T>inline void write(T x){
    if(x<0) putchar('-'),x=~(x-1); int s[40],top=0;
    while(x) s[++top]=x%10,x/=10; if(!top) s[++top]=0;
    while(top) putchar(s[top--]+'0');
}
string st;
bool later(char ch1,char ch2){
    //写了一个函数来判断ch1是不是在ch2后
    //同时,如果ch1完全等于ch2时,这说明我读了两遍一个词,这直接累加答案
    if(ch1==ch2) return 1;
    for(int i=0;i<st.size();++i){
        if(st[i]==ch1) return 0;
        if(st[i]==ch2) return 1;
    }
    return 0; //其实不会到这个时候了
}
int ans;
string st1;
int main(){
    cin>>st>>st1;
    char lch=st1[0];
    for(int i=1;i<st1.size();++i){
        if(later(lch,st1[i])) ans++;
        lch=st1[i];
    }
    cout<<ans+1<<endl;
    return 0;
}

T2 Even More Odd Photos

题面描述

Farmer John 正再一次尝试给他的 N 头奶牛拍照(2≤N≤1000)。
每头奶牛有一个范围在 1…100 之内的整数的「品种编号」。Farmer John 对他的照片有一个十分古怪的构思:他希望将所有的奶牛分为不相交的若干组(换句话说,将每头奶牛分到恰好一组中)并将这些组排成一行,使得第一组的奶牛的品种编号之和为偶数,第二组的编号之和为奇数,以此类推,奇偶交替。
Farmer John 可以分成的最大组数是多少?

输入格式(从终端/标准输入读入):

输入的第一行包含 N。下一行包含 N 个空格分隔的整数,为 N 头奶牛的品种编号。

输出格式(输出至终端/标准输出):

输出 Farmer John 的照片中的最大组数。可以证明,至少存在一种符合要求的分组方案。

输入样例1:

7
1 3 5 7 9 11 13

输出样例1:

3

样例解释1

在这个样例中,以下是一种分成最大组数三组的方案。将 1 和 3 分在第一组,5、7 和 9 分在第二组,11 和 13 分在第三组。

输入样例2:

7
11 2 17 13 1 15 3

输出样例2:

5

样例解释3:

在这个样例中,以下是一种分成最大组数五组的方案。将 2 分在第一组,11 分在第二组,13 和 1 分在第三组,15 分在第四组,17 和 3 分在第五组。

解题思路:

感觉就是一个大模拟,想了很久。详细内容不说了,见die码。附注,USACO给出的标准答案似乎die码复杂度比我的高:

算了,还是稍微讲一讲,避免未来又忘了这一题怎么做了。

首先的首先,会发现这一题其实数字大小没什么用,只是其奇偶性会改变很多。

ok首先,明显的一个贪心思路,就是说,如果我现在需要一个奇数,我肯定是上一个奇数,而不是上一个偶数加上一个奇数,因为明显,这个偶数可以放在后面一位来多创造一个价值。

根据这个思路,我现在先将偶数的数量和奇数的数量统计好。
然后现在判断是偶数数量多还是奇数数量多。
从简单开始,如果是偶数数量多,那么放偶数奇数偶数奇数,会发现放到最后一个奇数之后的那个偶数后,再多的偶数也只能叠加到前面去,创造不出一个新的奇数了,所以说这个答案比较简单,就是奇数数量*2+1。这是一个比较简单的情况。
如果一样多,那么答案很显然就是两个数量加起来\(\text{n}\)
由简至繁,如果现在是奇数数量多。
插一句题外话,为什么奇数数量多更加的复杂,其实很好理解,就是说,奇数数量多出来了,我两个奇数可以合成偶数,放一个奇数在别的地方会改变奇偶性,所以说明显是奇数数量多更加复杂。
好,回归正题,如果是奇数数量多的时候呢。不妨把可以消耗的偶数全部消耗完毕(反正迟早也是必须的嘛。
我现在的答案就是\(ans\times 2\)然后呢,我现在还剩奇数的个数减去偶数的个数个奇数(好像很绕)。
假设我现在还剩\(\text{pos}\)个奇数,貌似是个找规律呢,打个表。

其实表格是有一个很好看的的,但是我不会写\(\LaTeX\),罢了罢了。

\( \begin{matrix} pos \Delta ans\\ 1 \ \ -1\\ 2 \ \quad\ \ 1\\ 3 \ \quad\ \ 2\\ 4 \ \quad\ \ 1\\ 5 \ \quad\ \ 3\\ 6 \ \quad\ \ 4\\ 7 \ \quad\ \ 3\\ \end{matrix} \)

好,现在不难发现规律,规律就不用\(\LaTeX\)写了

蒟蒻die码:

#include <bits/stdc++.h>
#define pb push_back
using namespace std;
template <typename T>inline void read(T& t){
    t=0; register char ch=getchar();
    while(!('0'<=ch&&ch<='9')){if(ch=='-') t=-1;ch=getchar();}
    while(('0'<=ch&&ch<='9')){t=((t<<1)+(t<<3))+ch-'0'; ch=getchar();}
}
template <typename T,typename... Args> inline void read(T& t, Args&... args){
    read(t);read(args...);
}
template <typename T>inline void write(T x){
    if(x<0) putchar('-'),x=~(x-1); int s[40],top=0;
    while(x) s[++top]=x%10,x/=10; if(!top) s[++top]=0;
    while(top) putchar(s[top--]+'0');
}
int n;
int odd,even,ans;
int main(){
    cin>>n;
    while(n--){
        int t;
        read(t);
        if(t%2==0) even++;
        else odd++;
    }
    if(odd==even){
        cout<<odd<<endl;
        return 0;
    }
    //好,现在知道了这个过程,就模拟它
    //现在要构造第一组是偶数,第二组是奇数,就一直这样,那么先看是偶数多还是奇数多
    if(odd>even){
        //奇数多,答案先累加一下偶数
        ans=even*2;
        //要贡献even个奇数
        odd=odd-even;
        if(odd%3==1)
            cout<<(odd/3)*2-1+ans<<endl;
        else 
            if(odd%3==0)
                cout<<(odd/3)*2+ans<<endl;
            else
                cout<<(odd/3)*2+1+ans<<endl;
        return 0;
        //好,接下来一个应该是偶数然后是奇数
        //打个表看一看
        //还有一个就是说,如果现在还剩一个奇数,但是这一位要求一个偶数,那很难办,放在哪里都会打破平衡他本来的奇偶性,那么该怎么办呢。
        //奇数,那么想到的唯一一个办法就是少组两个,现在手里就肯定还会剩
        /*
        少组了两个,少组了两个是奇数和偶数
        这两个只可能是:
        偶数:两个奇数或者是一个偶数
        奇数:单个奇数
        那么可以把奇数拆掉,换成偶数前面的一个奇数
        还剩一个,
        还剩两个的情况呢?
        还剩两个刚好搞成一个偶数,还剩三个呢?
        还剩三个刚好两个,而且并不会引起问题
        还剩四个呢,四个的话就必须3+1,所以也只有两个
        五个呢,先2+1+2
        六个呢,就是2+1+2+1
        七个呢,就是2+1+3+1
        所以答案要先除以3
        除以3之后看除以3之后的余数
        比如说7/3余1 那么答案就是(7/3)*2
        四个呢 (4/3)*2
        如果余2呢
        (5/3)*2+1
        余0 (6/3)*2
        */
    }else{
        //剩下的情况就是说我现在偶数的数量比奇数的数量要多了,那么比较简单了,
        /*
        我先上一个偶数
        再上一个奇数
        如此反复,一直上到没有了奇数
        那我现在在上所有偶数
        上了多少个数了(奇数数量*2+1)
        */
       cout<<odd*2+1<<endl;
    }
    return 0;
}

T3 Just Stalling

题面描述

Farmer John 有 N 头奶牛(1≤N≤20),高度为 a1…aN。他的牛栏有 N 个牛棚,高度限制分别为 b1…bN(例如,如果 b5=17,那么一头高度不超过 17 的奶牛可以住在牛棚 5 里)。Farmer John 有多少种不同的方式安排他的奶牛,使得每头奶牛均住在不同的牛棚里,并且使得每个牛棚的高度限制均得到满足?

输入格式(从终端/标准输入读入):

输入的第一行包含 N。第二行包含 N 个空格分隔的整数 a1,a2,…,aN。第三行包含 N 个空格分隔的整数 b1,b2,…,bN。所有的高度和高度限制均在范围 [1,109] 内。

输出格式(输出至终端/标准输出):

输出 Farmer John 可以将每头奶牛安排到不同的牛棚里,使得每个牛棚的高度限制均得到满足的方法数。注意输出的数量可能需要使用 64 位整数型,例如 C++ 中的 long long。

输入样例:

4
1 2 3 4
2 4 3 4

输出样例:

8
在这个例子中,我们不能将第三头奶牛安排到第一个牛棚里,因为 3=a3>b1=2。类似地,我们不能将第四头奶牛安排到第一或第三个牛棚里。一种符合高度限制的安排方式为将奶牛 1 安排到牛棚 1,奶牛 2 安排到牛棚 2,奶牛 3 安排到牛棚 3,奶牛 4 安排到牛棚 4。

测试点性质:

测试点 1-5 满足 N≤8。
测试点 6-12 没有额外限制。

解题思路

其实,一开始的时候我这题根本就做不出来,因为我这一题将高度这个重要条件省略了,我直接用高度模拟出这个二分图中那两条边可以连接,然后一直在想优化暴力的方法。直到我突然想到,我的方法不论怎么优化,都必须把每一种方式都搜一遍,然后这个答案在\(\text{long long}\)之内,不爆炸才怪。

然后,稍微看了一眼答案,感觉做法非常巧。

主要是强行构造乘法原理可以套上去的状态,然后再进行计算。

#include <bits/stdc++.h>
#define pb push_back
using namespace std;
template <typename T>inline void read(T& t){
    t=0; register char ch=getchar();
    while(!('0'<=ch&&ch<='9')){if(ch=='-') t=-1;ch=getchar();}
    while(('0'<=ch&&ch<='9')){t=((t<<1)+(t<<3))+ch-'0'; ch=getchar();}
}
template <typename T,typename... Args> inline void read(T& t, Args&... args){
    read(t);read(args...);
}
template <typename T>inline void write(T x){
    if(x<0) putchar('-'),x=~(x-1); int s[40],top=0;
    while(x) s[++top]=x%10,x/=10; if(!top) s[++top]=0;
    while(top) putchar(s[top--]+'0');
}
int n,a[114],b[514];
bool cmp(const int &infoa,const int &infob){
    return infoa>infob;
}
long long ans=1;
int main(){
    cin>>n;
    for(int i=1;i<=n;++i) cin>>a[i];
    for(int i=1;i<=n;++i) cin>>b[i];
    sort(a+1,a+n+1,cmp);
    for(int i=1;i<=n;++i){
        int sum=0;
        /*
        现在将奶牛的高度按降序来排一下。
        然后一个一个牛棚去考虑
        主要是想用乘法原理算。
        怎么算,就是说,我现在知道了
        第i个位置有sum个位置可以放。
        这时候会想到,i-1个位置中放的地方,一定在这sum个东西中间
        那么我这一位其实只sum-i+1种方法可以选择。这种方法是对的么?
        */
        for(int j=1;j<=n;++j)
            if(b[j]>=a[i]) sum++;
        ans*=(sum-i+1);
    }
    cout<<ans<<endl;
    return 0;
}

完结撒花!

上一篇:箭头函数


下一篇:名字匹配否?