【题解】2023年第六届传智杯初赛第二场题解

补题链接: https://ac.nowcoder.com/acm/contest/72369

本次初赛第二场的题目也来源于牛客公共题库,基本是校招/竞赛原题或者经典题。考察的知识较为基础。

初赛第二场共8题:ABCDEFGH,其中B组6题:ACDEFG,A组6题:BDEFGH。

A 字符逆序

题目来源:牛客公开经典题目

alt

知识点:字符串

思路:直接调用系统自带函数,或者倒序遍历输出都可以。

参考代码(python):

print(input()[::-1])

参考代码(c++):

#include <bits/stdc++.h>
using namespace std;

int main() {
    string s;
    getline(cin,s);
    reverse(s.begin(),s.end());
    cout<<s;
}

B 喵

题目来源:牛客小白月赛37:https://ac.nowcoder.com/acm/contest/11214/J

alt

知识点:字符串

忽略字符串最后三个字符,输出即可。

参考代码(python):

a=input()
print(a[:-3])

参考代码(c++):

#include <bits/stdc++.h>
using namespace std;

int main() {
    string s;
    getline(cin,s);
    cout<<s.substr(0,s.length()-3);
}

C 小红的ABC

题目来源:牛客小白月赛41:https://ac.nowcoder.com/acm/contest/11218/B

知识点:字符串,枚举

思路:首先回文串有个性质:如果存在长度超过1的回文子串,那么最短回文子串的长度要么是2要么是3(因为如果存在一个长度为的回文子串,那么去掉首尾一定能得到一个长度为的回文子串)。因此直接特判这两种情况即可。(该做法复杂度

当然本题数据范围较小,直接暴力枚举每个子串判回文,然后维护合法最小长度也是可以通过的。纯暴力的复杂度为,可以通过剪枝优化到或者

参考代码:

#include <bits/stdc++.h>
using namespace std;

int main() {
    string s;
    cin>>s;
    int i;
    for(i=1;i<s.length();i++)if(s[i]==s[i-1])return cout<<2,0;
    for(i=2;i<s.length();i++)if(s[i]==s[i-2])return cout<<3,0;
    cout<<-1;
}

D 小红取数

题目来源:牛客题霸-动态规划专项练习:小红取数

alt

知识点:动态规划

思路:我们计为:取前个数,模的限制下的最大和,那么可以转移全部情况。

参考代码:

#include <bits/stdc++.h>
using namespace std;
long long dp[1010][1010];
int main() {
    long long n,k,i,x,j;
    cin>>n>>k;
    for(i=1;i<k;i++)dp[0][i]=-1e17;
    for(i=1;i<=n;i++){
        cin>>x;
        for(j=0;j<k;j++)dp[i][j]=dp[i-1][j];        //不取x
        for(j=0;j<k;j++){
            dp[i][(j+x)%k]=max(dp[i][(j+x)%k],dp[i-1][j]+x);    //取x
        }
    }
    cout<<(dp[n][0]>0?dp[n][0]:-1);
}

E 憧憬

题目来源:牛客小白月赛39:https://ac.nowcoder.com/acm/contest/11216/A

alt

知识点:枚举,计算几何

思路:我们直接枚举所有情况即可。注意判向量平行可以把除法转化为乘法来规避可能存在的精度问题。

参考代码:

#include<bits/stdc++.h>
using namespace std;

#define ll long long
int dis[4000040],vis[404040];
int a[1010][7];
int main(){
    ll t=1;
    //cin>>t;
    while(t--){
        ll n,i,k,j;
        cin>>n;
        for(i=0;i<n;i++)cin>>a[i][0]>>a[i][1]>>a[i][2]>>a[i][3],a[i][4]=a[i][2]-a[i][0],a[i][5]=a[i][3]-a[i][1];
        int x,y,xx,yy;
        cin>>x>>y>>xx>>yy;
        xx-=x,yy-=y;
        int jud=0;
        for(i=0;i<n;i++){
            for(j=i+1;j<n;j++){
                ll x4=a[i][4]+a[j][4],y4=a[i][5]+a[j][5];
                if(x4*yy==y4*xx)jud=1;
            }
        }
        if(jud)cout<<"YES";
        else cout<<"NO";
    }
}

F 加减

题目来源:牛客小白月赛37:https://ac.nowcoder.com/acm/contest/11214/I

alt

知识点:排序、前缀和、双指针/二分

思路:显然优先变化尽量接近的数。所以可以先对数组进行排序。

引理:一个数组每次操作可以让一个数加一或者减一,使所有数都相等的操作最小次数的方式是所有数都变成中位数(或两个中间的数的任意一个,若共有偶数个数)。

证明:一个数组有 个数比中位数小, 个数(或 个数,若共有偶数个数)比中位数大,假设把所有数都变成中位数的总操作次数为 。这时如果让所有数变成比中位数更小的数,相当于对于 个数而言 ,操作次数变少了,但对于 个数而言 ,操作次数变多了同样的数值。总体来说操作次数一定是变多的。如果让所有数变成比中位数更大的数同理。证毕。

通过以上引理,我们只需要求出最大的一个区间,令其所有数都变成中位数的次数不超过 即可。区间内部的操作次数可以通过前缀和达成 查询。

如何求这个最大区间呢?有两个方法:二分答案或者双指针。

方法一:二分答案

很明显,一定存在一个数 ,满足至少存在一个长度为 的区间满足其所有数都变成中位数的次数不超过 次,且不存在一个长度为 的区间使得其所有数都变成中位数的次数不超过 次。那么这个 就满足可以用二分答案来求的性质。 复杂度:二分单次判断的复杂度为 ,总复杂度为

方法二:双指针

当固定左端点 时,我们可以找到右边第一个不合法的右端点 ,当 向右移动时, 也向右移动,移动到最新的第一次不合法的位置。维护 的最大值即可。 双指针复杂度为 ,但还有排序的 ,总复杂度为

本题参考代码如下(双指针法):

#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll a[100010],k,n;
ll sum[100010];
int main(){
    ll i,j,temp;
    cin>>n>>k;
    ll s=0;
    
    for(i=0;i<n;i++)cin>>a[i];
    sort(a,a+n);
    for(i=0;i<n;i++)sum[i+1]=s+=a[i];
    
    i=j=temp=0;
    ll res=0;
    for(i=0;i<n;i++)res+=abs(a[i]-a[n/2]);
    if(res<=k){
        cout<<n;
        return 0;
    }
    ll ma=0;
    for(i=0;i<n;i++){
        j--;
        do {
            j++;
            temp=(i+j)/2;
            res=(temp-i)*a[temp]-(sum[temp]-sum[i]);
            res+=sum[j+1]-sum[temp+1]-(j-temp)*a[temp];
        } while (j<n&&res<=k);
        ma=max(ma,j-i);
    }
    cout<<ma;
}

G 白魔法师

题目来源:牛客小白月赛25:https://ac.nowcoder.com/acm/contest/5600/C

alt

知识点:树,dfs,并查集

思路:如果将一个黑色点染成白色,那么将得到一个白色连通块,这个连通块由和这个黑色点连结的所有白色连通块组成。

如果将一个白色点染成白色,那么不会有任何变化。

所以我们可以先并查集预处理一下,把所有白色连通块的大小求出来,并把所有白色点对应的连通块表示一下。连通块的大小可以dfs或者统计并查集根的孩子总数得出。

然后统计所有黑色点的邻点连通块大小即可。要注意特判全部是白色点的情况。

参考代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
vector<ll>g[200020];
string s;
ll fa[200020],kdm[200020],sz[200020];
ll f(ll x){
    if(fa[x]==x)return x;
    return f(fa[x]);
}
void uni(ll x,ll y){
    ll p=f(x),q=f(y);
    if(p!=q){
        if(kdm[p]<kdm[q]){
            fa[p]=q;
            kdm[q]+=kdm[p]+1;
        }
        else{
            fa[q]=p;
            kdm[p]+=kdm[q]+1;
        }
    }
}
int main(){
    ll n,i,j,k,x,y;
    cin>>n>>s;
    for(i=1;i<=n;i++)fa[i]=i;
    for(i=1;i<n;i++){
        cin>>x>>y;
        g[x].push_back(y);
        g[y].push_back(x);
        if(s[x-1]=='W'&&s[y-1]=='W')uni(x,y);
    }
    for(i=1;i<=n;i++)
        if(s[i-1]=='W')sz[i]=kdm[f(i)]+1;
    ll ma=0;
    for(i=1;i<=n;i++){
        if(s[i-1]=='B'){
            ll sum=1;
            for(j=0;j<g[i].size();j++)
                if(s[g[i][j]-1]=='W')sum+=sz[g[i][j]];
            ma=max(ma,sum);
        }
    }
    if(ma==0)cout<<n;
    else cout<<ma;
}

H 紫妹永不服输

题目来源:牛客小白月赛37:https://ac.nowcoder.com/acm/contest/11214/E

alt

知识点:构造

思路:

引理:由 个 R 、 个 P 组成的字符串,其中 'RP' 子序列的数量和 'PR' 子序列的数量之和为

证明:

一个字符串共有 个 R 、 个 P ,那么在这些 R 和 P 中各选一个, 方案数共有 种,要么构成 'PR' , 要么构成 'RP'。其必为原串的一个子序列。 因此 'RP' 子序列的数量和 'PR' 子序列的数量之和为 。证毕。

因此我们需要构造一个由 个 R 、 个 P 组成的字符串,其中 满足 。也就是说,我们需要找到 的两个因子,它们的乘积等于 且和不大于100000。

所以我们可以采取贪心的策略,取尽可能接近 的两个因子即可。

关于具体的构造方法:

如果构造成 RR..RRPP...PP ( 个R, 个P) ,此时共有 个 'RP' 。

这时我们如果把最后一个 R 向右移动一为,则可以转化一个 'RP' 为 'PR' 。如果把最后一个 R 放到所有 P 的后面,那么转化了 个 PR。以此类推,我们可以找到最终字符串的形态。

参考代码:

#include<bits/stdc++.h>
using namespace std;
int main(){
    long long n,m;
    cin>>n>>m;
    long long sum=n+m;
    int x=1,i;
    for(i=2;i*i<=sum;i++){
        if(sum%i==0)x=i;
    }
    if(x+sum/x>=100000){
        cout<<-1;
        return 0;
    }
    int y=sum/x;
    int left=n/y,right=m/y;
    if(n%y==0){
        while(left--)cout<<'R';
        while(y--)cout<<"P";
        while(right--)cout<<'R';
    }
    else{
        int yu=n%y;
        while(left--)cout<<'R';
        while(y--){
            
            cout<<"P";
            if(y==yu)cout<<'R';
            
        }
        while(right--)cout<<'R';
    }
}
全部评论
求原题链接
2 回复 分享
发布于 2023-12-13 10:17 河南
orz
点赞 回复 分享
发布于 2024-11-29 18:20 广东
求求大佬更新复赛第一场,我就做来一道题
点赞 回复 分享
发布于 2023-12-18 14:58 湖北

相关推荐

07-23 14:04
东北大学 C++
既然这样,为什么不点击就送呢
牛马88号:因为你合适。但有很多笔试就挂了、通过了再排序的
点赞 评论 收藏
分享
酷酷我灵儿帅:这去不去和线不线下面说实话没啥关系
点赞 评论 收藏
分享
评论
17
23
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务