AtCoder Beginner Contest 148 题解
前言
包含题目:
ABC148 A ABC148 B ABC148 C ABC148 D ABC148 E ABC148 F
这次比赛坑好多啊(虽然水了),也可能是我的实现方法不对qaq。
A - Round One
题意
你有三个数:(1, 2, 3),现在其中两个数不对,输出对的那个数。
做法
建立一个集合,删去坏的,输出剩下的一个。
用三个if
语句判断即可。
程序
考场做法:
#include<bits/stdc++.h>
using namespace std;
set<int> t={1,2,3};
int a,b;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>a>>b;
t.erase(a);
t.erase(b);
cout<<*t.begin()<<endl;
return 0;
}
真实做法:
#include<bits/stdc++.h>
using namespace std;
int a,b;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
//三行头文件,可以优化cin、cout速度(但仅能聊以慰籍)
cin>>a>>b;
if(a>b)swap(a,b);//swap用作交换两个同一类型的变量的值,此处确保a<b
if(a==1&&b==2)cout<<3;
if(a==1&&b==3)cout<<2;
if(a==2&&b==3)cout<<1;
return 0;
}
B - Strings with the Same Length
题意
给你长度(N)的字符串(S)和(T),让你把它们合并起来,方法如下:
拿(S)的开头字符,删去,添加到答案串的末尾。拿(T)的开头字符,删去,添加到答案串的末尾。
重复以上操作(N)次,此时(S)和(T)应该是空串。
做法
暴力模拟即可,不过不用真的删去字符,从前往后跑就可以了。
程序
#include<bits/stdc++.h>
using namespace std;
int n;
string s,t,ans;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>s>>t;
for(int i=0;i<n;i++){//从前往后跑s和t的字符
ans+=s[i];
ans+=t[i];//向ans的末尾添加字符
}
cout<<ans<<endl;
return 0;
}
C - Snack
题意
给你(A)和(B),让你求一个数,使得它能被(A)和(B)整除吗,同时最小(也就是最小公倍数)。
做法
使用辗转相除法先求出最大公因数,对于(A)和(B),它们的递归的最大公因数求法(f(A,B))如下:
(保证它们都是正整数)
- (B)为(0),此时返回(A)。
- 返回(f(B,Amod B))。
(A imes B div f(A,B))就是最小公倍数。
程序
#include<bits/stdc++.h>
using namespace std;
int gcd(int a,int b){//用前文的方法求最大公因数
if(b==0)return a;
else return gcd(b,a%b);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int a,b;
cin>>a>>b;
cout<<((long long)(a/gcd(a,b)))*b<<endl;//要用long long,不然会爆int
return 0;
}
D - Brick Break
题意
给你(N)个砖块,每个砖块写有数字(a_i),问你删去一些砖块(最多删去(N-1)块)后,剩下的(K)块砖块,是一个形如(1,2,3,dots,K)的序列时,(K)最大是多少,如果不能有这样的序列,那么就输出-1
。
做法
其实就是求一个形如(1,2,3,dots,K)的(a_i)的子序列(子序列的定义是元素可以不连续的),可以(O(N))完成。
不考虑-1
,只求出(K)最大是多少,那么如果(K=0),就输出-1
,否则输出(K)。
(K)的求法:
记录当前的序列的最后一个的值(c)(也就是序列的长度)(初始为(0)),从前往后跑(a_i)中的元素,如果是(c+1),那么就把(c)加一。这样最后它就是(K)了。
程序
#include<bits/stdc++.h>
using namespace std;
int n;
int a[200005];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
int c=1;//此处c的值是当前序列的最后一个的值加一,当时写的时候太急,失误了
for(int i=0;i<n;i++){
cin>>a[i];
if(a[i]==c){//如同做法中描述的,是序列长度加一时,就增加序列长度
c++;
}
}
cout<<(n-c+1==n?-1:n-c+1)<<endl;//序列长度为c-1,那么n-c+1就表示删去的东西的个数
return 0;
}
E - Double Factorial
题意
给你一个数(n)。
定义一个函数(f(n)):
- (f(n)=1) (if (n<2))
- (f(n)=nf(n−2)) (if (n≥2))
输出函数值的末尾的0
的个数。
做法
很容易就可以看出,后缀零的个数只和函数值(2)和(5)因子的个数有关。
由于(f(n))只要求了(f(n-2)),所以奇偶性肯定不会变的,(n)为奇数时不可能包含(2)作为因数,特判一下。
我们再来看偶数,那么我们可以这么计算(2)的因子的个数:
(i)从(1)开始递增,查看包含(2^i)作为因子的数的个数(也就是(lfloor { frac{n}{2^i} } floor),此处(lfloor a floor)指不超过(a)的最大整数,也就是下取整),把函数值中的(2)的因子个数加上这个个数,你可能会问了,它包含的因数不应该是(lfloor { frac{n}{2^i} } floor imes i)吗?其实,里面的(lfloor { frac{n}{2^i} } floor imes ( i - 1 )),已经在(lfloor { frac{n}{2^{i-1}} } floor)中加过了,所以就是(lfloor { frac{n}{2^i} } floor)就可以了。
对于(5)的因数,我们可以大致使用同样的方法求,但是每次对于答案的贡献是(lfloor { frac{n}{2 imes 5^i} } floor),因为必须是偶数,所以需要再乘一个(2)以确保只计算了偶数。具体原因如下:
我们举个例子,列出(1,2,3,dots,25)的(5)的因子的个数:
总和是(6)。
那么,只计算偶数的话,就只有这样的序列:
((N)代表奇数,舍去了)总和是(2)。
现在可以理解为什么要乘以(2)了吧。
程序
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,c2,c5;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
if(n&1){
cout<<0<<endl;
return 0;
}
for(ll i=2;i<=n;i*=2){
c2+=n/i;
}
for(ll i=10;i<=n;i*=5){
c5+=n/i;
}
cout<<min(c2,c5)<<endl;
return 0;
}
F - Playing Tag on Tree
题意
有一棵(N)个结点的树,高桥和青木在上面的(u)和(v)结点上玩抓人游戏。每个回合如下:
- 如果高桥和青木在同一个结点上,游戏结束。高桥选一个和当前结点相邻的结点,走过去。
- 如果高桥和青木在同一个结点上,游戏结束。青木选一个和当前结点相邻的结点,走过去。
- 开始下一个回合
轮到某人走时,不能停留在同一个结点。
高桥希望回合数尽可能多,青木希望回合数尽可能少,两人都采用最优策略。
输出游戏结束时青木走的步数。
做法
首先,青木的最优策略很好确定,就是走通向的当前高桥的位置的简单路径就可以了。同时,不论高桥的位置怎样变化,青木走的路径都会沿着通向最终会抓到高桥的结点的这条道路,证明很难,我就不证明了。感性地理解,就是由于高桥和青木不会在途中相遇,所以青木走向的那个(以当前青木所在结点为根的)子树肯定包含高桥和游戏结束时的高桥的终点。
高桥的呢,有点麻烦,所以我们就搜索一下吧qwq(暴力、大好き)。
首先,我们预处理出青木到每个结点的距离,之后搜索高桥的路径(深搜、广搜皆可)。对于当前结点是否可以走,只需要满足高桥到这里的距离严格小于青木到这里的距离即可。特殊地,由于只能移动,不能停留在同一个结点,所以到了叶子结点时,高桥会在相邻结点和这个叶子结点之间重复移动,最终抓到的位置一定是在相邻结点上(一共两种情况,自己手推一下就好了)。
程序
#include<bits/stdc++.h>
using namespace std;
int n,ta,ao,ans;
vector<int> g[100005];
int dis[100005];
void dfs(int x,int p,int d){
if(d>=dis[x]){//如果被抓到了就退出
ans=max(ans,dis[x]);
return;
}
for(int i=0;i<g[x].size();i++){
int &y=g[x][i];
if(y!=p){
dfs(y,x,d+1);
}
}
if(g[x].size()==1){//是叶子结点,就特判一下之前做法中提到的情况
ans=max(ans,dis[x]-1);
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>ta>>ao;
for(int i=1;i<n;i++){
int a,b;
cin>>a>>b;
g[a].push_back(b);
g[b].push_back(a);
}
memset(dis,-1,sizeof(dis));
dis[ao]=0;
{//预处理青木到每个结点的距离dis
queue<int> q;
q.push(ao);
while(!q.empty()){
int x=q.front();q.pop();
for(int i=0;i<g[x].size();i++){
int &y=g[x][i];
if(dis[y]==-1){
dis[y]=dis[x]+1;
q.push(y);
}
}
}
}
dfs(ta,-1,0);
cout<<ans<<endl;
return 0;
}
结束语
感谢观看,感觉自己写得很high,完全没有考虑到别人的观感qaq。所以,有什么意见就评论吧<3<3<3,我一定会改的qaq。