第十五届蓝桥杯C++B组国赛题解+复盘总结

文章目录

  • 1、合法密码
  • 2、选数概率
  • 3、蚂蚁开会
  • 4、立定跳远
  • 5、最小字符串
  • 6、数位翻转
  • 7、数星星
  • 8、套手镯
  • 9、挑石头
  • 10、最长回文前后缀
  • 总结

本人目标是国一,昨天模拟做了一遍,结果如下图,这水平感觉远远不够。
现在已经全部补完,发现了许多问题,好多该得到的分数没有得到,总结记录一下。
第十五届蓝桥杯C++B组国赛题解+复盘总结_第1张图片

1、合法密码

赛时暴力枚举,官方正解是把非字母非数字算作字符,这点与我的想法一致,但是代码就是写错了,在数字处忘记continue退出循环了,不够仔细。
答案是400

#include
#define LL long long
using namespace std;

typedef pair PII;

void solve(){
    string s="kfdhtshmrw4nxg#f44ehlbn33ccto#mwfn2waebry#3qd1ubwyhcyuavuajb#vyecsycuzsmwp31ipzah#catatja3kaqbcss2th";
    int n=s.size();
    s='#'+s;
    int ans=0;
    auto check=[&](string t)->bool{
        int ok1=0,ok2=0;
        for(auto c:t){
            //赛时
//            if(c>='0' && c<='9') ok1=1;
            //赛后
            if(c>='0' && c<='9'){
                ok1=1;
                continue;
            }
            if(c>='a' && c<='z') continue;
            if(c>='A' && c<='Z') continue;
            ok2=1;
        }
        if(ok1 && ok2) return true;
        return false;
    };
    for(int i=1;i<=n;i++){
        string t;
        if(i+7>n) break;
        for(int j=i;j<=i+6;j++) t+=s[j];
        for(int j=i+7;j<=n && j-i+1<=16;j++){
            t+=s[j];
            if(check(t)) ans++;
        }
    }
    cout<>T;
    while(T--){
        solve();
    }
    return 0;
}

2、选数概率

暴力枚举然后取gcd化简后判断分子分母就行了
答案是55,94,56

3、蚂蚁开会

赛时写的特别复杂:暴力枚举两个线段,先判断是否平行,还要判断是否是同一条直线,不是的话还要求交点,反正就是写了一大堆的数学式子去推,早该去想其他做法了,毕竟才第三题,不应该这么复杂。
简单做法:注意到线段数是0-500,坐标范围是0-10000,可以算出斜率然后暴力枚举线段上的点,再用map记录一下,出现数量大于等于2才记录答案,详情看代码。

#include
#define int long long
using namespace std;

typedef pair PII;

void solve() {
    int n;
    cin>>n;
    vector> a(n+1);
    for(int i=1; i<=n; i++) {
        cin>>a[i][0]>>a[i][1]>>a[i][2]>>a[i][3];
    }
    map mp;
    for(int i=1;i<=n;i++){
        auto [x1,y1,x2,y2]=a[i];
        int dx=x2-x1,dy=y2-y1;
        int gcd=__gcd(abs(dx),abs(dy));
        dx/=gcd,dy/=gcd;
        for(int j=0;;j++){
            int x=x1+j*dx,y=y1+j*dy;
            mp[{x,y}]++;
            if(x==x2 && y==y2) break;
        }
    }
    int ans=0;
    for(auto [x,y]:mp){
        if(y>=2) ans++;
    }
    cout<>T;
    while(T--) {
        solve();
    }
    return 0;
}

4、立定跳远

二分即可,具体地,先让两点之间距离大于2L的填充检查点,使得最后剩下的都是小于等于2L的,记录一下数量,再除去小于等于L的,最后与检查点剩余数量+1(这个1是使用了爆发技能)进行比较。详情看代码

#include
#define int long long
using namespace std;

typedef pair PII;
int n,m;
const int N=1e5+10;
int a[N];
bool check(int L){
    int pre=0;
    int cnt=0;
    int r=m;
    for(int i=1;i<=n;i++){
        int x=a[i];
        if(x-pre>2*L){
            int need=(x-pre-2*L+L-1)/L;
            r-=need;
            if(r<0) return false;
            pre+=need*L;
        }
        if(x-pre>L) cnt++;
        pre=x;
    }
    if(r+1>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];
    int l=1,r=1e8;
    while(l<=r){
        int mid=l+r>>1;
        if(check(mid)) r=mid-1;
        else l=mid+1;
    }
    cout<>T;
    while(T--){
        solve();
    }
    return 0;
}

5、最小字符串

赛时大致思路对了,我是用两个队列进行模拟,对第二个字符串先进行排序,谁小谁就pop,然后加入答案,只不过在相等时没有想清楚,如果当前两个字符相等,一定是先拿出第一个,因为第一个字符串后面可能出现更小的字符,详情看代码。
这里没想清楚,真的不应该。

#include
#define LL long long
using namespace std;

typedef pair PII;

void solve() {
    string ans;
    int n,m;
    cin>>n>>m;
    string s,t;
    cin>>s>>t;
    sort(t.begin(),t.end());
    queue q1,q2;
    for(auto c:s) q1.push(c);
    for(auto c:t) q2.push(c);
    while(!q1.empty() && !q2.empty()) {
        char c1=q1.front();
        char c2=q2.front();
        if(c1<=c2) {
            q1.pop();
            ans+=c1;
        } else {
            q2.pop();
            ans+=c2;
        }
    }
    while(!q1.empty()) {
        ans+=q1.front();
        q1.pop();
    }
    while(!q2.empty()) {
        ans+=q2.front();
        q2.pop();
    }
    cout<>T;
    while(T--) {
        solve();
    }
    return 0;
}

6、数位翻转

简单dp
定义dp[i][j][0/1]表示前i个数中选择了j个区间进行翻转且第i个数是/否翻转。
详情看代码

#include
#define int long long
using namespace std;

typedef pair PII;
const int N=1e3+10;
int n,m;
int a[N],a1[N],dp[N][N][2];
int calc(int x){
    vector vec;
    while(x){
        vec.push_back(x%2);
        x/=2;
    }
    int res=0;
    for(auto t:vec){
        res*=2;
        res+=t;
    }
    return res;
}
void solve(){
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i],a1[i]=calc(a[i]);
//    for(int i=1; i<=n; i++) {
//        cout<=1) dp[i][j][1]=max(dp[i][j][1],dp[i-1][j-1][0]+a1[i]);
        }
    }
    int ans=0;
    for(int i=0;i<=m;i++){
        ans=max(ans,max(dp[n][i][0],dp[n][i][1]));
    }
    cout<>T;
    while(T--){
        solve();
    }
    return 0;
}

7、数星星

算是诈骗题吧
就是问有多少个不同的菊花图,暴力枚举每个点,假设点i的度数为x,那么就是C(x,l-1)+C(x,l)+C(x,l+1)+…+C(x,r-1),当然有些不存在的需要特判掉,我们可以使用map标记度数为x时的组合数相加的和,这样后面再枚举到就不用重复计算,对于不重复计算的,最坏情况的复杂度大概就是1+2+3+4+5+6…+n??反正大概是这样,也就是说暴力枚举(l-1,r-1)的不会特别多。
此外,需要注意2个点组成一棵树的情况,要把这种情况的总数除以2,详情看代码

#include
#define int long long
using namespace std;

typedef pair PII;
const int N=1e5+10,mod=1e9+7;
vector g[N];
int fact[N],infact[N],n;
int qmi(int a,int b) {
    int res=1;
    while(b) {
        if(b&1) res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res;
}
void init() {
    fact[0]=infact[0]=1;
    for(int i=1; i<=100000; i++) {
        fact[i]=fact[i-1]*i%mod;
        infact[i]=qmi(fact[i],mod-2);
    }
}
int C(int a,int b) {
    if(a>n;
    int l,r;
    for(int i=1; i<=n-1; i++) {
        int u,v;
        cin>>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    cin>>l>>r;
    l--,r--;
    for(int i=1; i<=n; i++) vis[i]=-1;
    int ans=0;
    for(int i=1; i<=n; i++) {
        int x=g[i].size();
        if(vis[x]==-1) {
            int res=0;
            if(x>=l) {
                for(int j=l; j<=min(x,r); j++) {
                    res=(res+C(x,j))%mod;
                }
            }
            vis[x]=res;
        }
        ans=(ans+vis[x])%mod;
    }
    if(1>=l && 1<=r){
        int sum=0;
        for(int i=1;i<=n;i++){
            int x=g[i].size();
            if(x>=1){
                sum=(sum+x)%mod;
            }
        }
        ans=(ans-sum+mod)%mod;
        sum=sum*qmi(2,mod-2)%mod;
        ans=(ans+sum)%mod;
    }
    cout<>T;
    while(T--) {
        solve();
    }
    return 0;
}

8、套手镯

这题赛时思路完全错了:想着矩形一定是以某个圆的正方形左下角为边界,然后n^2暴力枚举就完了,大错特错了。
正解思路:
首先对于矩形的放置,我们有两个维度需要考虑x 和 y。因此我们需要先固定一维,然后枚举另一个维度。
那怎么固定呢?其实仔细思考一下,在最优的矩形放置策略中,我们是不是将矩形的底部刚好与某个圆的底部相切是最优的。其实显然,因为上诉操作会尽可能的留空间给上面的圆。
因此,对于固定的 y,我们只需要枚举每个圆的最底部即可。
可以先将圆按照右端点升序排序,然后双指针,对于 x 这个维度我们也需要像维度 y一样贪心地考虑,将矩形的右部刚好与某个圆的右部相切是最优的。
因此,我们需要用一个优先队列对在矩形中的圆的最左端点进行维护,这样在遍历右端点的时候我们可以及时且高效地将左端点越出矩形左边界的点删除。然后依次遍历维护最大点数(优先队列的大小的最大值)即可。
详情看代码

#include
#define LL long long
using namespace std;

typedef pair PII;
const int N=1010;
struct node{
    int x,y,r;
    bool operator<(const node p)const{
        return x+r,greater> q;
    for(int i=1;i<=n;i++){
        if(a[i].y-a[i].ryr) continue;
        if(2*a[i].r>w) continue;
        int xr=a[i].x+a[i].r;
        int xl=xr-w;
        while(!q.empty() && q.top()>n>>w>>h;
    for(int i=1;i<=n;i++){
        cin>>a[i].x>>a[i].y>>a[i].r;
        below[i]=a[i].y-a[i].r;
    }
    sort(a+1,a+n+1);
    sort(below+1,below+n+1);
    int ans=0;
    for(int i=1;i<=n;i++){
        ans=max(ans,calc(below[i]));
    }
    swap(h,w);
    for(int i=1;i<=n;i++){
        ans=max(ans,calc(below[i]));
    }
    cout<>T;
    while(T--) {
        solve();
    }
    return 0;
}

9、挑石头

这题更不应该了,赛时写的类似dp记忆化搜索,但是题目读错了,看成是i下标的集合而不是c[i]分数集合,改成c[i]就全过了,真是可惜,虽然这里数据点比较水全部过了但感觉还是能卡掉的。

#include
#define LL long long
using namespace std;

typedef pair PII;
const int N=4e4+10;
int n,a[N];
set dp[N];
set dfs(int x){
    if(dp[x].size()!=0) return dp[x];
    set res;
    res.insert(a[x]);
    if(x*2<=n){
        set t=dfs(x*2);
        for(auto it:t) res.insert(it);
    }
    if(x+a[x]<=n){
        set t=dfs(x+a[x]);
        for(auto it:t) res.insert(it);
    }
    dp[x]=res;
    return dp[x];
}
void solve(){
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    int ans=0;
    for(int i=1;i<=n;i++){
        ans=max(ans,(int)dfs(i).size());
    }
    cout<>T;
    while(T--){
        solve();
    }
    return 0;
}

可以用bitset优化一下,降低一点转移的复杂度?

#include
using namespace std;

int n, c[40005];
bitset<40005> bs[40005];

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++)
        cin >> c[i];
    int ans = 0;
    for(int i = n; i >= 1; i--)
    {
        bs[i].set(c[i]);
        if(i + c[i] <= n)
            bs[i] |= bs[i + c[i]];
        if(i * 2 <= n)
            bs[i] |= bs[i * 2];
        ans = max(ans, (int)bs[i].count());
    }
    cout << ans << "\n";
    
    return 0;
}

10、最长回文前后缀

赛时写的暴力枚举删除哪一段,然后拼接起来哈希+二分求,脑子完全抽了,完全不用哈希+二分,暴力枚举就能拿20%的分数。。。

正解的话也是哈希+二分,先把前后缀相同的删掉,对剩余的字符串哈希一下,然后枚举删除前面一段/后面一段,进行二分求最长相同的长度。
详情看代码

#include
#define LL long long
using namespace std;

typedef pair PII;
typedef unsigned long long ULL;
const int N = 500010;
const ULL base = 13331;
ULL h[N],hr[N],p[N];
ULL get(ULL H[],int l,int r){
    return H[r]-H[l-1]*p[r-l+1];
}
int calc(string s){
    int n=s.size();
    s='#'+s;
    p[0]=1;
    for(int i=1;i<=n;i++){
        h[i]=h[i-1]*base+s[i];
        hr[i]=hr[i-1]*base+s[n-i+1];
        p[i]=p[i-1]*base;
    }
    int ans=0;
    for(int i=1;i>1;
            if(!mid || (get(h,1,mid)==get(hr,i+1,i+mid))){
                l=mid+1;
            }else{
                r=mid-1;
            }
        }
        ans=max(ans,l-1);
    }
    return ans;
}
void solve() {
    string s;
    cin>>s;
    int n=s.size();
    s='#'+s;
    int l=1,r=n;
    while(l=r){
        cout<>T;
    while(T--) {
        solve();
    }
    return 0;
}

总结

总体来说还是很可惜的,算法都会,没有考到特别难的算法
像1,5,9这四个题基本算是纯纯失误导致的
第3题想复杂了
第8思路是错了但是我后面尝试最简单的暴力做法就是暴力枚举矩形的左下角然后去看有多少个圆在里面也能过70%的数据,而自己写了半天只有15%

你可能感兴趣的:(蓝桥杯,蓝桥杯,c++)