前言
这场比赛跟之前的ABC比赛比起来难度还是要高不少,前面的几题也没有之前的比赛那么签,中间的中档题也要难不少(个人感觉),下面的题解,我赛时过了的题我会尽量讲一下我做题时候的心理过程,是如何想出答案的,赛时没过但是后面补题了的题,我就尽量讲一下我对于题解的理解。
A.Online Shopping(模拟)
考察英语阅读理解
这题还是比较简单,就是网购,告诉我们要买的每个物品的数量和价格,以及满多少钱包邮,问最后付的钱多少,直接按题意模拟即可。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e5+10;
int p[maxn],q[maxn];
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n,s,k;cin>>n>>s>>k;
int tot=0;
for(int i=1;i<=n;i++){
cin>>p[i]>>q[i];
tot+=p[i]*q[i];
}
if(tot<s) tot+=k;
cout<<tot<<endl;
return 0;
}
B.Glass and Mug(模拟)
题意
给我们玻璃杯和马克杯,然后有三种跟倒水相关的操作,总共执行 k k k次,问 k k k次后最终玻璃杯和马克杯还剩多少水。
分析
同样按照题意模拟这三种操作,可以用两个变量来记录当前的玻璃杯和马克杯的水量,然后三种操作就对应对这两个变量进行操作。不过要注意 i f if if语句的顺序一定要按照它给的三种操作顺序,(一开始没看懂第一种操作,我就先 i f if if第二种情况,后面再 e l s e i f else if elseif第一种情况,样例没过,才注意到一定要按照给的顺序判断)
代码
#include<bits/stdc++.h>
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n,G,M;cin>>n>>G>>M;
int g=0,m=0;
for(int i=1;i<=n;i++){
if(g==G){
g=0;
}
else if(!m){
m=M;
}
else{
int tem=min(G-g,m);
m-=tem;
g+=tem;
}
}
cout<<g<<" "<<m<<endl;
return 0;
}
C.T-shirts(贪心)
题意
这题是真正的阅读理解
给一个含012的字符串,有两种衣服一般的和豪华的,一开始有 m m m件一般的衣服,1和2代表两种活动,1的活动两种衣服都可以穿,2的活动只能穿豪华衣服,0代表洗衣服,即两种衣服的数量恢复到原有的数量,问最少需要买多少件衣服才能满足活动需要
分析
首先因为豪华衣服在两种活动都能穿,所以贪心地想,要买衣服肯定买的是豪华衣服。然后字符串里每次遇到0就会重置衣服数量,所以肯定要以0为分界线,分成若干个段,满足最少买衣服数量且能满足这些段的要求。
然后赛时我一下子脑抽了,没想到在不确定买多少件衣服的情况下要怎么做,就写了个二分,二分购买豪华衣服的数量再 O ( n ) O(n) O(n),然后就可以简单地遍历模拟是否符合条件。
正解是直接统计0划分出的若干个段中,1、2活动的数量分别为 x x x, y y y,然后因为开始一件豪华衣服也没有,所以至少要买的数量大等于 y y y,而且如果活动1过多,自己原有的普通衣服不够穿,那么还需要买豪华衣服使得活动1也够穿,也就是购买的豪华衣服数量大等于 x + y − m x+y-m x+y−m,所以答案就是若干段中的 m a x ( x + y − m , y ) max(x+y-m,y) max(x+y−m,y)的最大值。
代码(二分)
#include<bits/stdc++.h>
using namespace std;
int n,m;string str;
bool check(int mid){
int s=m,p=mid;
for(auto c:str){
if(c=='0'){
s=m;p=mid;
}
else if(c=='1'){
if(s) s--;
else if(p) p--;
else return 0;
}
else{
if(p) p--;
else return 0;
}
}
return 1;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>m;
cin>>str;
int l=0,r=1e3,ans=0;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid)) ans=mid,r=mid-1;
else l=mid+1;
}
cout<<ans<<endl;
return 0;
}
D.Swapping Puzzle(枚举/全排列)
题意
给两个 n ∗ m n*m n∗m的二维矩阵 ( n , m ≤ 5 ) (n,m\leq 5) (n,m≤5),可以执行若干次操作,每次操作将第一个矩阵的相邻两行或者相邻两列进行交换,问将矩阵一变成矩阵二至少需要经过多少次操作,或者不可能使矩阵一变成矩阵二。
分析
赛时这题没做出来,不过还是讲一下我赛时的心路历程。
我先去观察,交换行列对矩阵的影响是什么。会发现假如只交换列,那么对于行来说,下标相同的元素都是一块变换的,也就是同一行的元素只是在同一行内部移动,不会跑到其他行。同理交换行,对于每一列来说,元素只在每一列内部移动不会跑出去。
就算两种操作结合起来,依旧如此,所以判定是否有解的情况,我想的就是,能否将矩阵一的每一行对应矩阵二的某一行,并且满足这两行中的元素对应大小的数量相同,也就是矩阵二的这一行能够由矩阵一的这一行交换后得到。然后由这种交换得到一种次序映射关系,将矩阵二的这一行设为 1 , 2 , 3 , 4 , 5 1,2,3,4,5 1,2,3,4,5,得到矩阵一的这一行对应为 2 , 1 , 4 , 5 , 3 2,1,4,5,3 2,1,4,5,3(举例)等等,然后其他行也都能够对应映射到不同的某一行,且映射的数字也要为 2 , 1 , 4 , 5 , 3 2,1,4,5,3 2,1,4,5,3,代表这一种映射是有效的,有解。
但是会发现上面讲的这种匹配方法太麻烦,而且如果有重复数字的话也会不知道该将哪两个数字进行匹配。所以这题就卡在这里了,赛后看了题解恍然大悟。
正解不是通过矩阵一和矩阵二的元素去进行匹配,而是去枚举第二个矩阵所有行列交换后可能得到的矩阵,再检验矩阵一和矩阵二是否满足这种枚举的匹配。
对于行来说,所有行的排列方式为 n ! n! n!种,列同理为 m ! m! m!,我们相当于是去将矩阵二的行列所有可能的交换形式后得到的新矩阵去跟矩阵一匹配,而这所有可能的交换形式最多也就是 n ! ∗ m ! n!*m! n!∗m!即 125 ∗ 125 125*125 125∗125种。
PS:枚举全排列可以用c++的next_permutation()函数,不用写dfs,具体使用方法可以去搜一下,或者直接看我代码里面的用法。
具体的这种操作方法就可以开两个数组 r o w [ n ] row[n] row[n]和 c o l [ m ] col[m] col[m],分别表示矩阵二的第 i i i行,对应交换后得到的新矩阵第 r o w [ i ] row[i] row[i]行,以及矩阵二的第 j j j行,对应交换后得到新矩阵的第 c o l [ i ] col[i] col[i]列。
然后我们要做的就很简单了,就是检查这个新矩阵是否和矩阵一相等,直接 O ( n 2 ) O(n^2) O(n2)的遍历矩阵一,一个一个元素检查即可(发现有一个元素不满足直接 b r e a k break break,可以优化一点时间)
然后假如已经得到可以匹配了,代表一定有解,又该如何求总操作次数呢?我们不妨把行、列分开,因为行列之间的交换互不影响,那么单单看行,想要交换回去,其实就相当于把我们枚举到的排列 2 , 1 , 4 , 5 , 3 2,1,4,5,3 2,1,4,5,3通过相邻元素交换,使其变回 1 , 2 , 3 , 4 , 5 1,2,3,4,5 1,2,3,4,5,那么这不就是经典的冒泡排序吗?所以我们就可以知道操作次数就等于排列的逆序对数,再 O ( n 2 ) O(n^2) O(n2)的遍历统计枚举的排列的逆序对数即可。
将每一种可能的枚举排列的行、列的逆序对数相加,取 m i n min min,即为最终答案。总的时间复杂度是 O ( n ! ∗ m ! ∗ n ∗ m ) O(n!*m!*n*m) O(n

本文是一场C++算法比赛的题解,涵盖多道题目。包括模拟类的网购、倒水问题,贪心策略的T恤购买问题,枚举全排列的矩阵交换问题,状压dp的物品装袋问题,以及用线段树解决的数组操作期望问题,详细分析题意、思路并给出代码。
最低0.47元/天 解锁文章
694

被折叠的 条评论
为什么被折叠?



