A 如何才能防AK
前置知识:无
题意:
统计10出现的次数,若超过6个则输出 just so so,若为1~5个则输出 good,若无则输出 666。
解法:
签到题,用一个变量cnt来统计10出现的次数,然后输出即可
Code:
#include<bits/stdc++.h>
using namespace std;
int T,cnt;//全局变量 默认为0
int main()
{
scanf("%d",&T);
while(T--){ //先判断T是否为0,再将T减1,相当于循环T次
int tmp;
scanf("%d",&tmp);
if(tmp>=10) cnt++;
}
if(cnt==0) printf("666");
else if(cnt>=6) printf("just so so");
else printf("good");
return 0;
}
D 战力对比
前置知识:无
题意:
给一个数列,统计其中所有长度为K的子序列中元素最大值,然后在这些最大值中找到最大值。
解法:
在所有长度为K的子序列的最大值里,一定包括整个数列中元素的最大值
若在子序列最大值中再取最大值时,即为数列的最大值。
所以直接在输入的时候找到序列的最大值即可 (甚至都没用到K)
Code:
#include<bits/stdc++.h>
using namespace std;
int N,K,ans,tmp;
int main()
{
scanf("%d%d",&N,&K);
for(int i=1;i<=N;i++){
scanf("%d",&tmp);
ans=max(ans,tmp);//max函数,在两个元素里取最大值
}
printf("%d",ans);
return 0;
}
I 时间复杂度与TLE
前置芝士:string类型
double类型
题意:
给出时间限制t和复杂度数量级N以及程序的时间复杂度,判断当前程序的状态:超时/危险/安全
解法:
因为该题的数据范围有点大,对于
N
3
N^3
N3都超过了
l
o
n
g
long
long
l
o
n
g
long
long的数据范围,那么考虑高精度
那么考虑用double类型来存储N,double类型的数据范围很大,大概在10300左右
那如何判断当前的时间复杂度字符串呢?
在C语言中可以使用char数组以及它的比较函数strcmp来完成
这里提供C++中更方便的string类型来完成
string为C++自带的字符串类型,
与其他数据类型(int,double等等)一样,它也具有大小比较的功能
那就是:"<"、">"、"=="
没有看错,不需要调用其他的函数,直接使用大于小于符号即可
string类型的比较与strcmp相同,两个字符从左向右逐个字符相比,直到出现不同的字符或遇‘\0’为止
那么这题的代码就简单了,直接使用“==”判断是否为对应的复杂度:
Code:
#include<bits/stdc++.h>
using namespace std;
int T;
double num;
string str;//定义一个string类型的变量
int main()
{
scanf("%d%lf",&T,&num);
cin>>str;//string类型只能通过cin>>来进行输入,cin>>为C++中独有的输入模式
if(str=="n^2") num=pow(num,2);//pow为求幂的函数
else if(str=="n^3") num=pow(num,3);
else if(str=="nlogn") num=num*log2(num); //log2为底为2的求对数的函数
if(num<=T*1e7) printf("Safe");
else if(num<=T*1e8) printf("Dangerous");
else printf("TLE");
return 0;
}
E 希望得到的成绩
前置芝士:多组输入
排序函数
long long类型
题意:
多组输入,在给出的N门成绩中,选出K门最大的,并计算这K门成绩的平均值
解法:
不要被这个多组输入吓到了呀
就是你的代码要具有输入多组测试样例并输出的功能,具体看代码
对于排序而言,在C++的STL库中有排序的函数:sort()
具体用法为:sort( 排序数组的首地址, 排序数组的末地址+1, 排序的规则);
如果没有排序的规则,那么sort默认为升序排序
若要修改为降序排序,则排序的规则可以修改为:greater<int>()
在后续的学习中,会学习到更复杂的排序方式,如结构体排序等等
明显可见这个sort函数是一个前闭后开的结构,在后续的学习中会遇到很多这样的结构
该sort函数的时间复杂度为 O ( l o g 2 N ) O(log_2N) O(log2N),而普通的冒泡、选择排序 O ( N 2 ) O(N^2) O(N2),是无法通过本题1e5的数据范围的
举个栗子:
比如说要对数组中的第 0 号元素 A[0] 到第 4 号元素 A[4] 进行排序,那么则为:sort(A,A+5);
A就是数组的首地址,A+5就是数组的末地址+1
而自定义成降序排序,则为:sort(A,A+5,greater<int>());
在本题中,在完成了降序排序后,只需要对数组的前K个数求和然后求平均值即可
然而还有个坑点就是,在本题中,前K个数的值可能超过了int的范围,需要开long long
Code:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
int T,N,K;
int arr[MAXN];
int main()
{
scanf("%d",&T); //T组测试样例
while(T--){ //先判断T是否为0,再将T减1,相当于循环T次
scanf("%d%d",&N,&K);
for(int i=1;i<=N;i++) scanf("%d",&arr[i]); //不需要对数组清零,因为输入时即覆盖了
sort(arr+1,arr+1+N,greater<int>()); //对数组进行升序排序,由于是从1开始的,那么首末地址都需要加1
long long sum=0; //记录前K大的和
for(int i=1;i<=K;i++) sum+=arr[i];
printf("%.8lf\n",sum*1.0/K); //先乘1.0转换为double类型,由于需要在1e-6的误差内,保险起见输出小数点后8位
}
return 0;
}
G 回形取数求和
前置芝士: 标记数组
方向数组
题意:
给出一个N × \times ×M的矩阵,然后按下图所示的顺序来进行遍历:
然后对遍历的奇数项和偶数项分别求和输出。
解法:
这题最容易想到的方法就是按照题目的要求进行模拟,然后纪录
这里提供两个小技巧
首先是标记数组:
把走过的位置和不可走的位置(边界)标记为-1,而为0的地方就代表可以走的位置
方向数组:
定义两个大小为4的数组xx和yy,按照顺序分别存着右、下、左、上四个方向
然后按照这四个方向的顺序来遍历这个矩阵,
首先向右走,当遇到障碍时向下走,然后遇到障碍向左走,最后向上走,
而向上走遇到障碍的时候,方向就转变成了向右走,又回到了一开始向右走的状态
那这个不就是一个循环的结构嘛!?考虑用取余数来实现
具体实现见代码
Code:
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2005;
int arr[MAXN][MAXN];//原数组的值
int brr[MAXN][MAXN];//标记数组
int N,M,cnt=1,op=0;//cnt为已经遍历的方格数,op为当前的方向,op在0~3之间
int xx[]={0,1,0,-1};//xx和yy分别为方向数组
int yy[]={1,0,-1,0};
int x=1,y=1;//纪录当前的位置
long long ans1,ans2;//纪录奇数或偶数的值,数据爆int了!开longlong
int main()
{
memset(brr,-1,sizeof(brr)); //将brr数组按字节初始化为-1
scanf("%d%d",&N,&M);
for(int i=1;i<=N;i++)
for(int j=1;j<=M;j++){
scanf("%d",&arr[i][j]);
brr[i][j]=0; //brr为0时 则为当前位置可以走
}
while(cnt<=N*M){ //一共需要遍历N*M大小的矩阵
if(cnt%2==1) ans1+=arr[x][y]; //cnt为奇数时将其加入ans1中
else ans2+=arr[x][y];//当为偶数时加入ans2中
brr[x][y]=-1;//将当前位置标记为-1,代表当前位置走过了,不可再走
if(brr[x+xx[op]][y+yy[op]]==-1) op=(op+1)%4;//若行走的下一个位置不可走,则换方向,取余数即为让方向循环
x+=xx[op]; y+=yy[op]; //在当前方向(op)前进一个单位
cnt++;//同时将走过的位置数量加一
}
if(ans1==0) printf("-1\n");//若没有数纪录时,直接输出-1,下同
else printf("%lld\n",ans1);
if(ans2==0) printf("-1");
else printf("%lld",ans2);
return 0;
}
有更简单的方法:传送门
H Gcd and lcm
前置芝士:多组输入
结构体
递归
辗转相除法
题意:
给出a b c d,根据要求求出以下式子中的一个
解法:
熟悉的英文体面…
诶,这里又来了一个多组输入,这个多组输入跟前面的有啥区别呢?
woc,这个多组输入没有给有多少组!在文件读完时才算读入完毕,那这个咋整呢?
对于没有给定多少组输入时,有固定的输入模式:
while(scanf("%d",&N)!=EOF){
//循环体
}
//或者
while(~scanf("%d",&N)){
//循环体
}
//对于输入多个数据是一样的,在本题中应该如下所示
while(scanf(" %c %d/%d %d/%d",&ch,&A.zi,&A.mu,&B.zi,&B.mu)!=EOF){
//循环体
}
而在%c前面留一个空格就会消除输入中的空格和回车的影响,(rzp:讲过了讲过了,下一个)
在本题解法中定义了一个结构体来表示一个分数,在这里就不赘述结构体的用法,当然不用结构体也可以做
这里提一个知识点,关于最大公因数GCD和最小公倍数LCM:
两个数的GCD和LCM的乘积与这两个数的乘积相等,表示为:
A × B = = G C D × L C M A\times B==GCD\times LCM A×B==GCD×LCM
而求最大公因数GCD时,用辗转相除法+递归+三目运算符的组合拳,就只有一行的代码
那用上面的结论,求最小公倍数LCM时,也只有一行的代码了…
当然也可以调用编译器自带的最大公因数函数:__gcd(a,b)
long long gcd(long long a,long long b)
{
return b==0?a:gcd(b,a%b);
}
long long lcm(long long a,long long b)
{
return a/gcd(a,b)*b;
}
由于这道题的数据不讲武德,如果a和b都为1e9时,求LCM时,a × \times ×b就会爆int,则需要开longlong
所以该题的代码如下:
Code
#include<bits/stdc++.h>
using namespace std;
struct fenshu{//分数结构体
int zi,mu;
}A,B;
int gcd(int a,int b){return b==0?a:gcd(b,a%b);}//强行压行
int lcm(int a,int b){return a/gcd(a,b)*b;}
char ch;
int main()
{
while(scanf(" %c %d/%d %d/%d",&ch,&A.zi,&A.mu,&B.zi,&B.mu)!=EOF){
int g1=gcd(A.zi,A.mu);
int g2=gcd(B.zi,B.mu);
A.zi/=g1; A.mu/=g1;
B.zi/=g2; B.mu/=g2;
fenshu ans;
if(ch=='G'){
ans.zi=gcd(A.zi,B.zi);
ans.mu=lcm(A.mu,B.mu);
}
else{
ans.zi = lcm(A.zi,B.zi);
ans.mu = gcd(A.mu,B.mu);
}
int GCD = gcd(ans.zi,ans.mu);
ans.zi/=GCD; ans.mu/=GCD;
if(ans.mu==1) printf("%d\n",ans.zi);
else printf("%d/%d\n",ans.zi,ans.mu);
}
return 0;
}
F 交际圈差距
前置芝士:并查集
题意:
有N个人,M个关系,问多个人连成的最大的圈子和最小的圈子的人数差值。
解法:
这题是并查集模版题,并查集在后续的学习中会进行讲解,在此处则不再赘述。
Code:
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5+5;
int N,M,a,b;
int Fa[MAXN];
int ans[MAXN];
void uni(int x,int y);
int find(int x);
int main()
{
scanf("%d%d",&N,&M);
for(int i=1;i<=N;i++) Fa[i]=i;
for(int i=1;i<=M;i++){
scanf("%d%d",&a,&b);
uni(a,b);
}
for(int i=1;i<=N;i++) ans[find(i)]++;
int mn=1e9,mx=-1e9;
for(int i=1;i<=N;i++){
if(ans[i]){
mx=max(mx,ans[i]);
mn=min(mn,ans[i]);
}
}
if(mx==N) printf("WOW");
else printf("%d",mx-mn);
return 0;
}
void uni(int x,int y)
{
Fa[find(x)]=find(y);
}
int find(int x)
{
if(x==Fa[x]) return x;
else return Fa[x]=find(Fa[x]);
}
C Pokémon Go
前置芝士:广度优先搜索 BFS
题意:
在N × \times ×M ( 1 ≤ N , M ≤ 1000 ) (1 \leq N,M \leq 1000) (1≤N,M≤1000)的矩阵中,给出K( 0 ≤ K ≤ 1 0 4 0\leq K \leq 10^4 0≤K≤104)个障碍,T( 1 ≤ T ≤ 10 1\leq T\leq 10 1≤T≤10)个出口,
共有P( 1 ≤ P ≤ 1 0 5 1\leq P\leq 10^5 1≤P≤105)组询问,在(x, y)位置能否走到出口,若可以则输出走到某个出口的最短路径
解法:
该题为略微改版的 BFS ,在后续的学习中会学到,在这里不讲述其原理,
最朴素的方法就是对于每一组询问进行一次 BFS ,那么最坏情况的时间复杂度为: O ( N M P ) O(NMP) O(NMP),必然不能通过
观察到出口的个数远少于询问的次数,考虑对出口进行 BFS
然后对可以走到的地点进行标记,若多次搜索到,则取其中的最小值
当查询时,直接输出矩阵中纪录下的该元素的具体值即可,时间复杂度为: O ( N M T + P ) O(NMT+P) O(NMT+P),可以接受。
Code:
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e3+5;
const int INF = 1e9+7;
struct node{int x,y,val;};
int N,M,K,T,P;
int arr[MAXN][MAXN];
int vis[MAXN][MAXN];
int xx[]={1,0,-1,0};
int yy[]={0,1,0,-1};
int a,b;
void bfs(int x,int y)
{
queue<node>Q;
Q.push(node{x,y,0});
arr[x][y]=0; vis[x][y]=1;
while(!Q.empty()){
node now = Q.front(); Q.pop();
for(int i=0;i<4;i++){
if(arr[now.x+xx[i]][now.y+yy[i]]!=-1&&!vis[now.x+xx[i]][now.y+yy[i]]){
vis[now.x+xx[i]][now.y+yy[i]]=1;
int tmp = arr[now.x+xx[i]][now.y+yy[i]];
arr[now.x+xx[i]][now.y+yy[i]]=min(now.val+1,tmp);
Q.push(node{now.x+xx[i],now.y+yy[i],min(now.val+1,tmp)});
}
}
}
}
void clear(){
for(int i=1;i<=N;i++)
for(int j=1;j<=M;j++)
vis[i][j]=0;
}
int main()
{
memset(arr,-1,sizeof(arr));
scanf("%d%d%d",&N,&M,&K);
for(int i=1;i<=N;i++)
for(int j=1;j<=M;j++)
arr[i][j]=INF;
for(int i=1;i<=K;i++){
scanf("%d%d",&a,&b);
arr[a][b]=-1;
}
scanf("%d",&T);
for(int i=1;i<=T;i++){
clear();
scanf("%d%d",&a,&b);
bfs(a,b);
}
scanf("%d",&P);
for(int i=1;i<=P;i++){
scanf("%d%d",&a,&b);
if(arr[a][b]!=INF) printf("%d\n",arr[a][b]);
else printf("-1\n");
}
return 0;
}
B 跳棋
前置芝士:深度优先搜索 DFS
记忆化
题意:
在下述的跳棋盘中进行操作:
在1和9号位置上的数字给定的情况下,将1~12数字分别不重复的放在棋盘上,
使所有的4个数字的线段的和相等,求出所有的解并输出
解法:
深度优先搜索 DFS 不在此处进行讲解,
第一想法是进行全排列,调用next_permutation()进行求解,但是时间复杂度最坏为 O ( 10 ! × 1 0 3 ) O(10!\times10^3) O(10!×103)
那么解法就一定是DFS+剪枝了,DFS很简单,直接对每个点进行试探即可,
观察可得这四个数字的和一定是26,那么显然有以下几种剪枝:
- 将这四个数字相加求和,若大于26,则返回
- 若一条线段中四个数字已经填上,若不等于26,则返回
写了这两条剪枝后,还是只能通过大概70%的数据
继续分析可得,固定的1号和9号,最多只有 12 × 11 = 132 12\times11=132 12×11=132种情况,而题目却会询问1000次
那么询问的时候一定会有重复的询问,那么在每次求解后,将求解后的答案纪录下来,
若下次再次询问与当前的值相同的情况时,直接输出记录下的答案即可。
Code:
#include<bits/stdc++.h>
using namespace std;
int a,b,flag;
int arr[15];
int ans[13][13][13][100]; //对答案进行记录,1维2维分别为a和b的值,三维为一条答案,四维为每条答案开辟的空间
int cnt[13][13]; //cnt[i][j]为记录当a=i,b=j时的答案个数
int mem[13][13]; //mem[i][j]用于记录当a=i,b=j时,是否已经求解过,并且记录是否有解
bool vis[15];
inline bool judge(int x){//两个剪枝的过程
if(arr[1]+arr[3]+arr[6]+arr[8]>26) return true;
if(arr[1]+arr[4]+arr[7]+arr[11]>26) return true;
if(arr[8]+arr[9]+arr[10]+arr[11]>26) return true;
if(arr[2]+arr[3]+arr[4]+arr[5]>26) return true;
if(arr[2]+arr[6]+arr[9]+arr[12]>26) return true;
if(arr[5]+arr[7]+arr[10]+arr[12]>26) return true;
if(x==6) if(arr[2]+arr[3]+arr[4]+arr[5]!=26) return true;
if(x==9) if(arr[1]+arr[3]+arr[6]+arr[8]!=26) return true;
if(x==12){
if(arr[1]+arr[4]+arr[7]+arr[11]!=26) return true;
if(arr[8]+arr[9]+arr[10]+arr[11]!=26) return true;
}
if(x==13){
if(arr[2]+arr[6]+arr[9]+arr[12]!=26) return true;
if(arr[5]+arr[7]+arr[10]+arr[12]!=26) return true;
}
return false;
}
void dfs(int x)
{
if(judge(x)) return;
if(x==9) {dfs(x+1); return;};//第九个点已经固定
if(x>=13){
cnt[a][b]++;
for(int i=1;i<=12;i++){
printf("%d ",arr[i]);
ans[a][b][i][cnt[a][b]]=arr[i];//记录下答案在第三维中
}
printf("\n");
flag=1;
return;
}
for(int i=1;i<=12;i++){
if(!vis[i]){
vis[i]=true;
arr[x]=i;
dfs(x+1);
arr[x]=0;
vis[i]=false;
}
}
return;
}
int main()
{
while(scanf("%d%d",&a,&b)!=EOF){//多组输入
if(mem[a][b]==-1) {printf("-1\n\n"); continue;}
if(mem[a][b]==1){//若为1或-1则已经求解过答案,直接输出即可
for(int i=1;i<=cnt[a][b];i++){
for(int j=1;j<=12;j++)
printf("%d ",ans[a][b][j][i]);
printf("\n");
}
printf("\n");
continue;
}
flag=0;
vis[a]=vis[b]=true;
arr[1]=a; arr[9]=b;
dfs(2);//从第二个点进行搜索
if(!flag) {printf("-1\n\n");mem[a][b]=-1;}
else {printf("\n"); mem[a][b]=1;}
vis[a]=vis[b]=false;
}
return 0;
}