蓝桥·算法双周赛


文章目录

  • 三带一
  • 数树数
  • 分组
  • 健身
  • 契合匹配
  • 奇怪的线段

一、三带一

本题思路:本题看了数据范围可以直接暴力解决。

#include <bits/stdc++.h>

int main()
{
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);std::cout.tie(nullptr);

  int T;
  std::cin>>T;

  while(T--){
    std::string pokes;
    std::cin>>pokes;

    bool flag=false;//判断是否找到三张牌相同
    for(int i=0;i<pokes.size();i++){
      int cnt=0;
      for(int j=0;j<pokes.size();j++){
        if(pokes[i]==pokes[j])
          cnt++;
      }

      if(cnt==3){//如果找到的话不需要继续往下找了直接退出即可
        flag=true;
        break;
      }
    }

    if(flag) std::cout<<"Yes"<<std::endl;
    else std::cout<<"No"<<std::endl;
  }
  return 0;
}

二、数树数

本题思路:本题发现位置的编号为x,如果下一步向左边那么此时所处位置为2x-1,否则就是2x。

#include <bits/stdc++.h>

int main()
{
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);std::cout.tie(nullptr);

  int n,q;
  std::cin>>n>>q;

  while(q--){
    std::string s;
    std::cin>>s;

    int res=1;
    for(char c:s)
      if(c=='L') res=res*2-1;
      else res*=2;
    std::cout<<res<<std::endl;
  }
  return 0;
}

三、分组

本题思路:本题可以采用二分的方式由于是需要从最大极差中找出最小的极差,所以我们首先将所有的身高从小到大进行排序处理,然后我们进行二分即可。

#include <bits/stdc++.h>

constexpr int N=1e5+10;

int n,k;
int h[N];

bool check(int mid)
{
  int t=h[1];//当前第一个元素为第一组
  int cnt=1;
  for(int i=2;i<=n;i++)
    if(h[i]-t>mid){//如果与当前组最矮身高的极差超过所给的极差那么需要分到另一个小组中去
      cnt++;
      t=h[i];
    }
  return cnt<=k;
}

int main()
{
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);std::cout.tie(nullptr);

  std::cin>>n>>k;
  for(int i=1;i<=n;i++) std::cin>>h[i];

  //按照身高从小到大排序
  std::sort(h+1,h+n+1);

  //这里我们通过对最大极差进行二分处理
  int l=0,r=h[n]-h[1];
  while(l<r){
    int mid=l+r>>1;
    //如果当前mid满足条件则我们向左继续满足条件的最小极差
    if(check(mid)) r=mid;
    else l=mid+1;
  }
  std::cout<<r<<std::endl;
  return 0;
}

四、健身

本题思路:本题是一道dp问题,首先我们考虑一下如果第i天不训练或者不能训练的话那么此时状态间转移方程是dp[i]=dp[i-1],如果第i天使计划中的某一天则我们需要满足条件需要在[i-2^k+1,i]这个区间中需要都是空闲的,我们可以利用前缀和来判断区间是否空闲问题。

#include <bits/stdc++.h>

typedef long long LL;
constexpr int N=2e5+10;

int n,m,q;
int sum[N];//利用前缀和来求前i天有几天空闲
LL sw[N];//用来表示第2^k天
LL dp[N];

int main()
{
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);std::cout.tie(nullptr);
  
  std::cin>>n>>m>>q;
  for(int i=1;i<=q;i++) {
    int x;
    std::cin>>x;
    sum[x]++;
  }

  //利用前缀和来求出前面空闲的天数
  for(int i=1;i<=n;i++) sum[i]+=sum[i-1];

  while(m--){
    int k;
    LL s;
    std::cin>>k>>s;
    sw[k]=std::max(sw[k],s);
  }

  for(int i=1;i<=n;i++){
    dp[i]=dp[i-1];
    for(int j=0;j<=n;j++){//这里类似于完全背包问题
      int k=i-(1<<j);
      if(k>=0&&sum[i]-sum[k]==0)//如果[i-2^k+1,i]空闲的话,那么此时需要更新
        dp[i]=std::max(dp[i],sw[j]+dp[k]);
      else break;
    }
  }

  std::cout<<dp[n]<<std::endl;
  return 0;
}

五、契合匹配

本题思路:首先先将s串扩大两倍然后进行KMP算法即可。

#include<bits/stdc++.h>

constexpr int N=2e6+10;

int n;
char s[N],p[N];//这里s是长串是用来匹配的那一个人,p为短串也就是匹配的那一个
int next[N];

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);std::cout.tie(nullptr);
    
    std::cin>>n>>s+1>>p+1;
    
    //将s串大小写先进行转换
    for(int i=1;i<=n;i++){
      if(std::isupper(s[i])) s[i]=std::tolower(s[i]);
      else s[i]=std::toupper(s[i]);
      s[i+n]=s[i];
    }

    //求next数组
    for(int i=2,j=0;i<=n;i++){
        while(j&&p[i]!=p[j+1]) j=next[j];
        if(p[i]==p[j+1]) j++;
        next[i]=j;
    }
    
    //KMP
    int res=N;
    for(int i=1,j=0;i<=n+n;i++){
        while(j&&s[i]!=p[j+1]) j=next[j];
        if(s[i]==p[j+1]) j++;
        if(j==n){
            res=std::min(res,std::min(i - n, n * 2 - i));
            j=next[j];
        }
    }
    
    if(res==N) std::cout<<"No"<<std::endl;
    else std::cout<<"Yes"<<std::endl<<res<<std::endl;
    
    return 0;
}

六、奇怪的线段

本题思路:本题可以使用先求出包含a的所有区间个数减去[a,b]即可得到答案,那么我们可以采用树状数组的方式来找出前缀和。

#include <bits/stdc++.h>

constexpr int N=2e6+10;

int n,q;
int tr[N];
int res[N];
int sum[N];//求前缀区间和

struct Seg//用来表示一个区间
{
  int l,r;
  int id,op;//id表示编号,op表示操作方式
}Segs[N];


bool cmp(const Seg&s1,const Seg&s2)//以右端点进行排序
{
  if(s1.r==s2.r){
    if(s1.l!=s2.l) return s1.l<s2.l;
    return s1.op<s2.op;
  }
  return s1.r>s2.r;
}

int lowbit(int x)
{
  return x&-x;
}

void add(int x,int c)
{
  for(int i=x;i<=N;i+=lowbit(i)) tr[i]+=c;
}

int query(int x)
{
  int res=0;
  for(int i=x;i;i-=lowbit(i)) res+=tr[i];
  return res;
}

int main()
{
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);std::cout.tie(nullptr);

  std::cin>>n>>q;
  for(int i=1;i<=n;i++){
    int l,r;
    std::cin>>l>>r;
    //这里需要注意区间左右端点的大小关系需要判断
    if(l>r) std::swap(l,r);
    //这里是维护[l,r]这个区间都加上1
    sum[l]++,sum[r+1]--;
    Segs[i]={l,r,0,0};
  }

  //求出区间前缀和
  for(int i=1;i<N;i++) sum[i]+=sum[i-1];

  for(int i=1;i<=q;i++){
    int a,b;
    std::cin>>a>>b;
    res[i]=sum[a];
    if(a>b) std::swap(a,b);
    Segs[++n]={a,b,i,1};
  }

  std::sort(Segs+1,Segs+n+1,cmp);
  for(int i=1;i<=n;i++){
    if(Segs[i].op==0) add(Segs[i].l,1);
    else res[Segs[i].id]-=query(Segs[i].l);
  }

  for(int i=1;i<=q;i++) std::cout<<res[i]<<std::endl;

  return 0;
}