2021牛客寒假算法基础集训营1(A B C D E F H I J)

2024-03-17 00:08

本文主要是介绍2021牛客寒假算法基础集训营1(A B C D E F H I J),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

比赛链接:这里

目录

    • OP
    • A 串
        • 思路
        • 代码
    • B 括号
        • 思路
        • 代码
    • C 红和蓝
        • 思路
        • 代码
    • D 点一成零
        • 思路
        • 代码
    • E 三棱锥之刻
        • 思路
        • 代码
    • F 对答案一时爽
        • 思路
        • 代码
    • H 幂塔个位数的计算
        • 思路
        • 代码
    • I 限制不互素对的排列
        • 思路
        • 代码
    • J 一群小青蛙呱蹦呱蹦呱
        • 思路
        • 代码
    • ED

OP

个人能力有限,有的题目实在知识点欠缺太多,可以参照一下其他大佬的全题解。
&感谢ph大佬的全程指导。

A 串

递推。

思路

我们可以先算出长度为 1,2,…,n 的合法串的数量,再进行累加。

介于数据量(2 < n <= 106),通过排列组合来求几乎不可能,单纯打表可能就需要几十分钟。

接下来就是具体的递推过程:

假设长度为 n 的合法字符串有 An 个。长度为 n + 1 的字符串中的前 n 项可以分为两种情况:合法的与非法的。

——对于前 n 项为合法字符串的 n + 1 长字符串,显然情况有 An * 26 种(即第 n + 1 项可以为任意字母);
——对于前 n 项为非法字符串的 n + 1 长字符串,只有前 n 项存在 u 的字符串可以变为合法串,所以含 u 的 n 长字符串的数量为 26n - 25n 个,其中有 An 个合法串,所以此种情况的 n + 1 长合法串有 26n - 25n - An 个(即存在 u 的 n 长非法字符串数量为 含 u 的 n 长字符串数量 减去 合法的 n 长字符串数量,该种串后接 s 即变为合法串)。

所以得到递推公式 An+1 = 25 * An + 26n - 25n

代码
#include <bits/stdc++.h>
using namespace std;
const long long MOD = 1e9 + 7;
long long qs(long long a,long long t)//快速幂取模
{long long ans=1;while(t){if(t&1)ans=ans*a%MOD;a=a*a%MOD;t>>=1;}return ans%MOD;
}
int main()
{long long a=1,i,j,n,ans=1;cin>>n;for(i=3;i<=n;i++){a=a*25+qs(26,i-1)-qs(25,i-1);a%=MOD;if(a<0)a+=MOD;ans+=a;//累加ans%=MOD;}printf("%lld",ans);return 0;
}

B 括号

此题为特判题(Special Judge)

思路

首先我们观察样例,即可得知总括号对数为 每一个左括号后面的右括号总数的累加和。

所以说,为使总字符量尽可能少,我们需要把 k 先粗略地拆成 l1 * r1 的形式( l1 * r1 <= k ), l1 与 r1 需要尽可能接近(均值不等式);

我们可以取 l1 = ⌊ sqrt( k ) ⌋,则 r1 = ⌊ k / l1 ⌋,此时还需要补上差值 d = k - l1 * r1

接下来就是构造了:我们可以依次输出 l1 个左括号,r1 - d 个右括号,1 个左括号,d 个右括号。此时总对数则为 l1 * r1 + d 。

要求是非空串,注意对 0 特判。

代码
#include <bits/stdc++.h>
using namespace std;int main()
{int k,l,r,d,i;scanf("%d",&k);if(k==0){printf("(");return 0;}l=(int)sqrt(k);r=k/l;d=k-r*l;for(i=1;i<=l;i++)printf("(");for(i=1;i<=r-d;i++)printf(")");printf("(");for(i=1;i<=d;i++)printf(")");return 0;
}

比赛时使用的另一种方法,即把 k 拆成 n1 * a12 + n1 * a12 + … 的形式,再构造输出,代码如下,可ac,仅做参考。

#include <bits/stdc++.h>
using namespace std;int main()
{int k,i,ma,j;scanf("%d",&k);if(k==0){printf("(");return 0;}int a[31623]={0};ma=(int)sqrt(k);while(k){a[(int)sqrt(k)]++;k-=(int)sqrt(k)*(int)sqrt(k);}for(i=1;i<=ma;i++){printf("(");if(a[i]){for(j=1;j<=i*a[i];j++)printf(")");}}return 0;
}

C 红和蓝

dfs,涂色

思路

使用两遍bfs,第一遍进行分组和可行性判定,第二遍进行染色

具体实现上(也可以使用链表存图):

第一次dfs时,先递归处理下级节点,再将其与上级节点分入同一组,同时完成可行性判定;

第二次dfs时,判断其与上级节点是否在同一组,在即涂与上级相同的颜色,不在即将自己涂成与上级不同的颜色;

代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
struct node{int to, next;
}edge[N];
int head[N], cnt;//cnt为道路数组序号
int mrk[N], col[N], num;//num计组数
bool flag;void add(int u, int v)
{edge[++cnt] = {v, head[u]};//标记前向标head[u] = cnt;//更新待标记前向标
}void dfs1(int u, int fa)
{int son = 0;for(int i = head[u]; i != -1; i = edge[i].next){int v = edge[i].to;if(v != fa){son++;dfs1(v, u);}}if(son == 0 || mrk[u] == 0)//叶节点与未被下级节点涂色的节点需被涂色{if(mrk[fa] != 0)//如果其上级节点已被涂色{flag = 0;return ;}mrk[u] = mrk[fa] = ++num;//此节点与上级节点标成同一数字}
}void dfs2(int u, int fa)
{for(int i = head[u]; i != -1; i = edge[i].next){int v = edge[i].to;if(v == fa) continue;if(mrk[v] == mrk[u])//同组即同色col[v] = col[u];else col[v] = !col[u];//异组即异色dfs2(v, u);}
}int main()
{int n;cin >> n;memset(head, -1, sizeof(head));for(int i = 1; i < n; i++){int u, v;scanf("%d%d",&u,&v);add(u, v);add(v, u);}flag = true;dfs1(1, 0);if(flag == false || mrk[0] != 0)//后者是填色溢出{printf("-1");return 0;}dfs2(1, 0);for(int i = 1; i <= n; i++){printf("%c",(col[i] == 0)?'R':'B');}return 0;
}

D 点一成零

bfs,并查集

思路

假设图中共有p组连通块,每一块包含 c i c_i ci格,则总方案数即为 p ! ∗ ∏ i = 1 p c i p!*\prod_{i=1}^{p}c_i p!i=1pci

那么我们需要做的便是求出p与 c i c_i ci

我们可以用遍历网格进行bfs创建并查集,并用并查集维护更改;

最开始抱有侥幸心理,我想对于每一次更改,遍历所有连通块以重新计算ans,结果爆了TLE;

对于具体的维护过程:
如果新增了一个面积为c的连通块,则 a n s = a n s ∗ ( p + 1 ) ∗ c ans=ans*(p+1)*c ans=ans(p+1)c
进一步地,如果一个面积为b的连通块,由于方块更新,并入了一个面积为a的连通块,则对这两个连通块来说(即不包括点的那个块,不过该块同理,面积为1) a n s = a n s / p / a / b ∗ ( a + b ) ans=ans/p/a/b*(a+b) ans=ans/p/a/b(a+b);逆元处理除法即可;

使并查集的根为连通块中 i ∗ n + j i*n+j in+j最小的点

代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll M=1e9+7,N=502;pair<int ,int> fa[502][502];//father
map<pair<int,int>,int>cnt;//以[i][j]为根的并查集所代表的连通块的面积
queue<pair<int,int> >que;//bfs队列
int di[]={0,0,1,-1},dj[]={1,-1,0,0};
int mp[502][502],n,m,cou=0;ll qm(ll a,ll b)//快速幂
{ll ans=1;for(;b;b>>=1){if(b&1)ans=ans*a%M;a=a*a%M;}return ans;
}pair<int,int> find(pair<int,int> pi)//并查集find
{if(fa[pi.first][pi.second]!=pi)fa[pi.first][pi.second]=find(fa[pi.first][pi.second]);return fa[pi.first][pi.second];
}void bfs(pair<int,int> fat)
{int k;while(!que.empty()){pair<int,int>now=que.front();que.pop();for(k=0;k<4;k++){int in=now.first+di[k],jn=now.second+dj[k];if(in>=0&&in<n&&jn>=0&&jn<n&&mp[in][jn]&&fa[in][jn]==make_pair(in,jn)&&!(in==fat.first&&jn==fat.second)){cnt[fat]++;fa[in][jn]=fat;que.push({in,jn});//printf("*%d\n",cnt[fat]);}}}
}int main()
{int i,j,k;string g;cin>>n;for(i=0;i<n;i++){cin>>g;for(j=0;j<n;j++)mp[i][j]=g[j]-'0',fa[i][j]={i,j};}//图接收完毕ll ans=1;for(i=0;i<n;i++)for(j=0;j<n;j++)if(mp[i][j]&&fa[i][j]==make_pair(i,j)){cou++;cnt[{i,j}]=1;que.push({i,j});bfs({i,j});ans*=(ll)cou*cnt[{i,j}];//计算初始ansans%=M;}cin>>m;while(m--){int ci,cj;scanf("%d%d",&ci,&cj);if(mp[ci][cj])printf("%lld\n",ans);//如果本来就是1,则图无任何变化else{mp[ci][cj]=1;//假定其为单独块cnt[{ci,cj}]=1;cou++;ans*=cou;ans%=M;//其作为单独块,更新ans,后面再修正pair<int,int> to={ci,cj};for(k=0;k<4;k++)//判断该及块四周的连通块形成的新连通块的根{int in=ci+di[k],jn=cj+dj[k];if(in>=0&&in<n&&jn>=0&&jn<n&&mp[in][jn]){pair<int,int>fan=find({in,jn});if(fan.first*n+fan.second<to.first*n+to.second)to=fan;}}if(make_pair(ci,cj)!=to)//如果新块的根不是自身,将自身并入新块根所在旧块{pair<int,int>fan=find({ci,cj});fa[fan.first][fan.second]=to;ans*=qm((ll)cou*(cnt[to]*cnt[fan])%M,M-2);cou--;ans%=M;cnt[to]+=cnt[fan];ans*=cnt[to];ans%=M;}for(k=0;k<4;k++){int in=ci+di[k],jn=cj+dj[k];if(in>=0&&in<n&&jn>=0&&jn<n&&mp[in][jn]){pair<int,int>fan=find({in,jn});if(fan!=to)//把除新块根所在旧块以外的旧块并入新块根{fa[fan.first][fan.second]=to;ans*=qm((ll)cou*(cnt[to]*cnt[fan])%M,M-2);cou--;ans%=M;cnt[to]+=cnt[fan];ans*=cnt[to];ans%=M;}}}printf("%lld\n",ans);}}return 0;
}

E 三棱锥之刻

立体几何

思路

设中心点距面距离 ds ,距棱距离 da ,距顶点距离 dp ,喷漆半径 r
分为四种情况:

① r < ds
此种情况即无法喷到漆,面积为 0 ;

② ds <= r <= da
此种情况即每面被喷漆形状均为完整的圆,通过勾股定理求漆面半径,求圆面积即可;

③ da < r < dp
此种情况即为每面被喷漆形状为(三)弦切圆,如下图:
在这里插入图片描述

我将其分为三个三角形和一个扇形求面积,因为图中 k l 的长度均可求,便可使用 arccos 求出角度,进而得知扇形所占圆心角,进而求出扇形面积;

④ r >= dp
此时正四面体的四个面均被涂满,求三角形面积即可。

以下数据可参考,图源百度词条正四面体
在这里插入图片描述

代码
#include <bits/stdc++.h>
using namespace std;
#define PI acos(-1)int main()
{double r,a,d;cin>>a>>d;if(d*d-a*a/24<0) {printf("0");return 0;}//1r=sqrt(d*d-a*a/24);if(r<=a/2/sqrt(3))printf("%.5lf",4*PI*r*r);//2else if(r>a/2/sqrt(3)&&r<a/sqrt(3))//3{printf("%.5lf",(3*(sqrt(r*r-a*a/12)*a/2/sqrt(3))+PI*r*r*(2*PI-6*acos(a/2/sqrt(3)/r))/(2*PI))*4);}else printf("%.5lf",a*a*sqrt(3));//4return 0;
}

F 对答案一时爽

贪心

思路

最小值当然是 0 ,最大值即为两人答题总数 - 答案相异数。

假设一人全对,此时即为最大值。

代码
#include <bits/stdc++.h>
using namespace std;int main()
{int n,sa=0,i,c=0;char a[202],b[202],g;cin>>n;getchar();c=0;while(c!=n){g=getchar();if(g>='A'&&g<='Z')a[c++]=g;}getchar();c=0;while(c!=n){g=getchar();if(g>='A'&&g<='Z')b[c++]=g;}for(i=0; i<n;i++)if(a[i]==b[i])sa++;printf("%d %d",sa+n,0);return 0;
}

字符串处理一团糟…

H 幂塔个位数的计算

找规律

思路

具体推倒过程过于繁琐,主要是通过 a % 10 a\%10 a%10的结果推倒 a a % 10 a^{a}\%10 aa%10的结果,再进行迭代。
结果会在大概三层之内收束到定值。

代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
char a[100005],n[100005];
int main()
{scanf("%s%s",a,n);ll la=strlen(a),ln=strlen(n);if(ln==1 && n[0]=='1'){printf("%d\n",a[la-1]-'0');return 0;}else{int a1=a[la-1]-'0';int b=((a[la-2]-'0')*10+a[la-1]-'0')%4;if(a1==0 || a1==1 || a1==5 || a1==6 || a1==9){printf("%d\n",a1);}else if(a1==4){printf("6\n");}else if(a1==2 || a1==8){if(ln==1 && n[0]=='2' && b!=0){printf("4\n");}else{printf("6\n");}}else if(a1==3){if(b==1){printf("3\n");}else{printf("7\n");}}else if(a1==7){if(b==1){printf("7\n");}else if(b==3){printf("3\n");}}return 0;}
}

I 限制不互素对的排列

构造

思路

要用 [ 1 , n ] 的 n 个数构造出含 k ( k <= n / 2 ) 对非互质数对的数列,首先我们想到的就是用偶数构造所求部分,然后顺序排列其他数。

接下来除法默认向下取整

具体来说,对于某偶数 2 * i ,一定与 2 * ( i + 1 ) 不互质;对于某奇数 2 * i + 1 ,一定与 2 * ( i + 1 ) + 1 互质;对于 >= 2 的正整数 i ,一定与 i + 1 互质。

依照以上三条性质,如果输入值为 8 3 我们便可以构造出 [ 2 , 4 , 6 , 8 , 1 , 3 , 5 , 7 ] 与此同时,便发现了一个问题:对于前 n 个数,含有 ⌊ n / 2 ⌋ 个偶数,最多构成 ⌊ n / 2 ⌋ - 1 个偶数对,但是 k <= n / 2 。

解决此种问题,我们可以进一步处理,选择满足以下性质的某偶数:可以表示为 2 * ( 2 * i + 1 ) ,即可以拆成 2 和某奇数的积。将此偶数放在偶数部分末尾,后再接该奇数,则可保证有 k 对非互质数对。

对于该偶数,10,14,18,…,34,…,122,… 均满足条件,但是满足条件的最小数是 6 。取最小数除了方便处理之外,同时也方便可行判定。

此时,本题的输入便有三种情况:

① n < 6 && k = n / 2
此时无可行解,输出 -1;

② n > 6 && k < n / 2
此时输出前 k + 1 个偶数后,其余数依次输出即可;

③ n > 6 && k = n / 2
此时输出除 6 外的前 k - 1 个偶数后,输出 6 3 ,再依次输出其余数。

代码
#include <bits/stdc++.h>
using namespace std;int main()
{int n,k,i;cin>>n>>k;if(n<6&&k==n/2)printf("-1");//1else if(k==n/2)//3{for(i=1;i<=n/2;i++)if(i!=3)printf("%d ",2*i);printf("6 3 1 ");for(i=5;i<=n;i+=2)printf("%d ",i);}else//2{for(i=1;i<=k+1;i++)printf("%d ",2*i);for(i=1;i<=n;i++){if(i%2==0&&i/2<=k+1)continue;printf("%d ",i);}}return 0;
}

J 一群小青蛙呱蹦呱蹦呱

思路

我们手动模拟可知,剩下的数是质因子个数大于等于二的数,也就是说,剩下的数均可表示为 p1^m1 * p2^m2 * … * pn^mn ( n >= 2 )。如下图
在这里插入图片描述
对于范围内的所有质数 p[ i ] ,第 j 个剩下的数可以表示为 p1^m1j * p2^m2j * … * pn^mnj 则所有剩下的数的最小公倍数则为 p1^max( m11 , m12 , m13, … , m1j ) * p2^max( m21 , m22 , m23, … , m2j ) * … * pn^max( mn1 , mn2 , mn3, … , mnj) 。
接下来我们的任务就是求出 max() 。

对于 >= 2 的任意 p[ i ] ,小于 N (范围)且 含有 p[ i ] 幂次最大的 剩余数显然是 2 * p[ i ]^m ,则可以求出 m = logp[i]n/2;

对于质数 2 ,最大数则是 3 * 2m ,此时 m = log2n/3 。

之后累乘 p[ i ]m 即可。

代码
#include <bits/stdc++.h>
using namespace std;
const long long MOD = 1e9 + 7;
const long long N=80000007;
long long qs(long long a,long long t)//快速幂取模
{long long ans=1;while(t){if(t&1)ans=ans*a%MOD;a=a*a%MOD;t>>=1;}return ans%MOD;
}
static int p[N];
static bool n[N];int main()
{n[0]=1,n[1]=1;int i,j,c=0;for(i=2;i<=N;i++)//线性筛{if(!n[i])p[c++]=i;for(j=0;j<c&&i*p[j]<=N;j++){n[i*p[j]]=1;if(i%p[j]==0)break;}}int n;//与前面的数组名重复了,但是编译运行没有问题,可能不太好理解cin>>n;if(n<6){printf("empty");return 0;}long long ans=qs(2,(long long)log2(n/3.0));//对2for(i=1;p[i]<=n/2&&i<c;i++)//对其他{ans%=MOD;ans*=qs(p[i],(long long)(log2(n/2.0)/log2(p[i]*1.0)));//浮点除法&换底公式}printf("%lld",ans%MOD);return 0;
}

附:关于线性筛的优化,可以参照这里,(代码未体现)

ED

《 基 础 选 手 》

这篇关于2021牛客寒假算法基础集训营1(A B C D E F H I J)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/817197

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

poj 3974 and hdu 3068 最长回文串的O(n)解法(Manacher算法)

求一段字符串中的最长回文串。 因为数据量比较大,用原来的O(n^2)会爆。 小白上的O(n^2)解法代码:TLE啦~ #include<stdio.h>#include<string.h>const int Maxn = 1000000;char s[Maxn];int main(){char e[] = {"END"};while(scanf("%s", s) != EO

秋招最新大模型算法面试,熬夜都要肝完它

💥大家在面试大模型LLM这个板块的时候,不知道面试完会不会复盘、总结,做笔记的习惯,这份大模型算法岗面试八股笔记也帮助不少人拿到过offer ✨对于面试大模型算法工程师会有一定的帮助,都附有完整答案,熬夜也要看完,祝大家一臂之力 这份《大模型算法工程师面试题》已经上传CSDN,还有完整版的大模型 AI 学习资料,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

dp算法练习题【8】

不同二叉搜索树 96. 不同的二叉搜索树 给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。 示例 1: 输入:n = 3输出:5 示例 2: 输入:n = 1输出:1 class Solution {public int numTrees(int n) {int[] dp = new int

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念