2024牛客寒假算法基础集训营1 补题

2024牛客寒假算法基础集训营1 补题

A.DFS搜索 模拟

题意:

给你一个字符串 \(S\) ,求出 \(S\) 中是否存在子序列“DFS“"dfs"

思路:

直接模拟即可

参考代码

#include<bits/stdc++.h>

using namespace std;

#define ff first
#define ss second
#define eb emplace_back
#define all(u) u.begin(), u.end()
#define endl '\n'
#define debug(x) cout<<#x<<":"<<x<<endl;

typedef pair<int, int> PII;
typedef long long LL;
const int inf = 0x3f3f3f3f;
const int N = 1e5 + 10, M = 105;
const int mod = 1e9 + 7;
const int cases = 1;

void Showball(){
   int n;
   cin>>n;
   int c1=0,c2=0;
   while(n--){
    char c;
    cin>>c;
    if(c1==0&&c=='D') c1++;
    if(c1==1&&c=='F') c1++;
    if(c1==2&&c=='S') c1++;
    if(c2==0&&c=='d') c2++;
    if(c2==1&&c=='f') c2++;
    if(c2==2&&c=='s') c2++;
   }     
   cout<<(c1==3)<<" "<<(c2==3)<<endl;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int T=1;
    if(cases) cin>>T;
    while(T--)
    Showball();
    return 0;
}

B 关鸡 思维+模拟

题意:

有一个 \(2\times 2e9+1\) 的地图,有一只鸡在 \((1,0)\) ,鸡可以上下左右移动,但是不能通过有火的格子。

现在给出一些有火格子的坐标,问你最少还需要添加几个有火的格子,可以保证鸡走不出地图。

思路:

很明显,想要堵住鸡通过的路,有三种情况:

那么如果左边和右边都有前三种情况中的任意一种即可。

当然还有一种特殊情况,即图中的情况4。

这种情况只需要三个有火的格子即可。

判断以上情况是否存在,以及需要补充的块数即可。

参考代码

void Showball(){
   int n;
   cin>>n;
   set<int> f,s;
   while(n--){
    int r,c;
    cin>>r>>c;
    if(r==1) f.insert(c);
    else s.insert(c);
   }
   bool l=false,r=false;
   bool ll=false,rr=false;
   for(auto k:f){
     if(k<0){
       if(s.count(k-1)||s.count(k)||s.count(k+1)) ll=true;
       else l=true;
     }else if(k>0){
       if(s.count(k-1)||s.count(k)||s.count(k+1)) rr=true;
       else r=true;
     }
   }

   for(auto k:s){
      if(k<0){
        l=true;
     }else if(k>0){
       r=true;
     }
   }

   int ans1=3-s.count(0)-f.count(1)-f.count(-1);
   int ans=4;
   if(ll) ans-=2;
   if(rr) ans-=2;
   if(!ll&&(l||s.count(0))) ans--;
   if(!rr&&(r||s.count(0))) ans--;
   cout<<min(ans1,ans)<<endl;
}

C 按闹分配 前缀和

题意:

\(n\) 个人在同一个窗口办事,第 \(i\) 个人办事所需时间为 \(t_i\) ,定义 \(n\) 个人的不满意度 \(S\)\(n\) 个人各自办完事所需时间之和。

现在又来了一个人,办事需要 \(t\) 时间,他可以选择插队,插队后,后面的所有人必须等他办完事才可以办事。

现在给出 \(q\) 个询问,每次询问给出一个 \(m\) ,要求插队后的不满意度 \(S\) 增加不超过 \(m\) ,请求出每次询问这个人最早办完事的时间。

思路:

首先,根据题意,我们可知求出初始不满意度 \(S\) ,只需要对 \(t_i\) 进行从小到大排序,然后求前缀和即可。

我们考虑在 \(id\) 处插队对 \(S\) 的影响,很明显会让后面的 \(n-id\) 个人的办事时间增加\(t\),所以 \(S\) 会增加 \((n-id)\times t\)

那么由题意可知,\((n-id)\times t \leq m\) ,显然我们需要找到 \(id\) 的最小值,即为最优解。解不等式得:

\(id \geq n-m/t\) 。所以答案即为 \(s[id]+t\)

参考代码

void Showball(){
   int n,q,t;
   cin>>n>>q>>t;
   vector<int> a(n);
   vector<LL> s(n+1);
   for(int i=0;i<n;i++) cin>>a[i];
   sort(all(a));
   for(int i=0;i<n;i++) s[i+1]=s[i]+a[i];

   while(q--){
    LL m;
    cin>>m;
    LL id=max(0LL,n-m/t);
    cout<<s[id]+t<<endl;
   }
}

D 数组成鸡 数学 预处理

题意:

给你 \(1\) 个长度为 \(n\) 的数组,每次操作你可以将数组中的所有的值同时增加 \(1\),或者同时减少 \(1\)

你可以操作任意次。给 \(q\) 次询问,每次询问给你一个数 \(x\),询问是否可以通过操作使得数组所有值乘积

\(x\)

思路:

我们知道 \(1e9\) 范围内分解不同数的数量不超过20个。

那么我们就可以 \(O(400\sqrt{1e9})\) 预处理出所有的情况。

存到 \(set\) 中查询即可。

参考代码

const int K = 4e4;
void Showball(){
   int n,q;
   cin>>n>>q;
   vector<int> a(n);
   map<int,int> mp;
   for(int i=0;i<n;i++){
     cin>>a[i];
     mp[a[i]]++;
   } 
   sort(all(a));
   a.erase(unique(all(a)),a.end());
   int N=a.size();

   auto qmi=[&](int a,int b){//快速幂
     int res=1;
     while(b){
        if(b&1){
            if(1LL*res*a>=inf) return inf;
            res*=a;
        }
        a=min(1LL*inf,1LL*a*a);
        b>>=1;
     }
     return res;
   };

   set<int> s;
   s.insert(0);
   if(N<=20){
      for(int i=0;i<N;i++){
        for(int k=-a[i]-K;k<=-a[i]+K;k++){//-K<=a[i]+k<=K
          int res=1;
          for(int j=0;j<N;j++){
            int x=a[j]+k,y=mp[a[j]];
            int v=qmi(x,y);
            if(abs(1LL*res*v)>=inf){
                res=inf;
                break;
            }
            res*=v;
          }
          if(abs(res)<inf) s.insert(res);
        }
      }
   }

   while(q--){
    int x;
    cin>>x;
    cout<<(s.count(x)?"Yes\n":"No\n");
   }
}

E 本题又主要考察了贪心 搜索

题意:

\(n\) 个人进行比赛,目前第 \(i\) 个人已经获得了 \(a_i\) 分,接下来给你 \(m\) 组对阵表,两人对阵有三种结果:

获胜加 \(3\) 分,失败不加分,两人平局各加 \(1\) 分。

现在让你求出 \(1\) 号选手能够达到的最好排名。

思路:

看数据范围很小,果断搜索。我们注意到对局只有三种情况,那么直接枚举三种对局情况即可。

代码参考jiangly,自己赛时写的太臃肿了。

参考代码

void Showball(){
  int n,m;
  cin>>n>>m;
  vector<int> a(n),u(m),v(m);
  for(int i=0;i<n;i++) cin>>a[i];
  for(int i=0;i<m;i++){
    cin>>u[i]>>v[i];
    u[i]--,v[i]--;
  }

  int ans=n;
  auto dfs=[&](auto self,int i)->void{
    if(i==m){
        int res=1;
        for(int j=0;j<n;j++) res+=(a[j]>a[0]);//O(n)判断排名,不像我傻傻二分
        ans=min(ans,res);
        return;
    }
    for(auto [x,y]:{make_pair(3,0),{0,3},{1,1}}){//这一步枚举太简洁了
      a[u[i]]+=x;
      a[v[i]]+=y;
      self(self,i+1);
      a[u[i]]-=x;
      a[v[i]]-=y;
    }
  };

  dfs(dfs,0);
  cout<<ans<<endl;
}

F 鸡数题! 第二类斯特林数

题意:

求出长度为 \(m\) 且满足以下条件的数组的个数:

1.数组值全为正数

2.数组值严格单调增

3.数组值按位或之和为 \(2^n-1\)

4.任意两个数组值按位与结果为 \(0\)

思路:

根据题意,转化一下就是数组中的 \(m\) 个值,不能有相同的位数有值同时该位为 \(1\) ,并且要有\(n\)位该位为 \(1\)

那么问题转化为问题转变为 \(n\) 个不同的小球放在 \(m\) 个相同的箱子里,求方案数,这就是第二类斯特林数。

Stirling

参考代码

//预处理组合数
int fact[N],infact[N];
int qmi(int a,int k,int p){
    int res=1%p;
    while(k){
      if(k&1) res=(LL)res*a%p;
      a=(LL)a*a%p;
      k>>=1;
    }
    return res;
}
void init(int n){
  fact[0]=infact[0]=1;
  for(int i=1;i<=n;i++){
    fact[i]=(LL)fact[i-1]*i%mod;
    infact[i]=(LL)infact[i-1]*qmi(i,mod-2,mod)%mod;
  }
}
int C(int a,int b){
   return (LL)fact[a]*infact[b]%mod*infact[a-b]%mod;
}
//第二类斯特林数
int Stirling2(int n,int m){
  init(m);
  LL res=0;
  for(int k=0;k<=m;k++){
    if(k&1){
      res=(res-1LL*C(m,k)*qmi(m-k,n,mod)%mod+mod)%mod;
    }else{
      res=(res+1LL*C(m,k)*qmi(m-k,n,mod)%mod)%mod;
    }
  }
  return res*infact[m]%mod;
}
void Showball(){
   int n,m;
   cin>>n>>m;
   cout<<Stirling2(n,m)<<endl;    
}

G why送外卖 贪心 前缀和

题意:

现在有 \(n\) 张满减卷,第 \(i\) 张满 \(a_i\)\(b_i\) ,现在手里有 \(m\) 元,请问能够购买的最高原价是多少?满减卷可以叠加使用

思路:

因为满减卷可以叠加使用,所以我们可以按照原价从小到大排序,因为可以叠加使用,所以可以求一下前缀和,满足条件就更新答案。

参考代码

void Showball(){
   int n,m;
   cin>>n>>m;
   vector<PII> a(n);
   for(int i=0;i<n;i++) cin>>a[i].ff>>a[i].ss;
   sort(all(a));
   vector<LL> s(n+1);
   for(int i=0;i<n;i++) s[i+1]=s[i]+a[i].ss;

   LL ans=m;
   for(int i=0;i<n;i++){
     if(a[i].ff-s[i+1]<=m) ans=m+s[i+1];
   }    
   cout<<ans<<endl;
}

H 01背包,但是bit 位运算 lowbit

题意:

\(n\) 件物品,第 \(i\) 件物品价值为 \(v_i\),重量为 \(w_i\) 。总重量不再是普通的加和,而是所有物品按位或的结果。

求出总重量不超过 \(m\) 的情况下,所选物品的最大价值之和。

思路:

我们注意到按位或不能超过 \(m\) ,根据或的性质,那么如果 \(m\) 的第 \(i\) 位为 \(1\),那么被选物品第 \(i\) 位既可以是 \(0\) ,

也可以是 \(1\) 。反之,那么被选物品该位只能为 \(0\) 。我们仔细观察这个性质,发现 \(m \& w_i = w_i\)

所以满足这个性质的我们就可以进行选择。接着我们枚举 \(1\) ~ \(m\)。但是这样的时间复杂度是 \(O(mn)\)

需要优化,设我们当前枚举的数是 \(i\) ,我们注意到 \(i\) 的二进制中 \(0\) 越多,能够选中的物品就越少(因为条件更加苛刻)。比如说 \(x\)\(2\) 进制为 \(101101\) , 我们发现 \(x-1\)\(2\)进制为 \(101100\) , 那么 \(x-1\) 所能选中的物品 \(x\) 一定能够选中,所以说我们枚举了 \(x\) ,那么就不用枚举 \(x-1\) ,因为它并不会使结果更优。那么我们发现,每次都可以跳过lowbit(x)个,因为这些都不会使结果更优。

举个例子:

我们发现蓝色的就是跳过 \(lowbit(x)\)后的下一个。我们发现到下一个 \(lowbit(x)\) 之间的所有黑色的情况都不会比蓝色的情况更优,所以我们就可以不用枚举。这样时间复杂度就优化为了 \(O(n*log m)\)

考代码

void Showball(){
   int n,m;
   cin>>n>>m;
   vector<int> v(n),w(n);
   for(int i=0;i<n;i++) cin>>v[i]>>w[i];

   auto calc=[&](int x){//计算体重按位或小于等于x的价值之和
     LL res=0;
     for(int i=0;i<n;i++){
        if((x&w[i])==w[i]) res+=v[i];
     }
     return res;
   };

   LL ans=calc(m);
   for(int i=m;i;i-=(i&-i)){//lowbit(i)=i&-i;
     ans=max(ans,calc(i-1));
   }
   cout<<ans<<endl;
}

I It's bertrand paradox. Again! 思维

题意:

给出了两种生成 \(n\) 个圆的方式,对于生成圆的要求就是圆必须在 \(-100\leq x\leq100\)\(-100 \leq y \leq100\) 的平面区域中。

第一种方式:等概率在 \((-100,100)\) 选中圆心坐标,然后等概率在 \([1,100]\) 选中半径。如果生成的圆不满足要求,

一直重新随机生成半径,直到满足要求为止。

第二种方式:等概率在 \((-100,100)\) 选中圆心坐标,然后等概率在 \([1,100]\) 选中半径。如果生成的圆不满足要求,

一直重新随机生成圆心坐标和半径,直到满足要求为止。

思路:

注意到,第一种生成的圆的圆心会更加分散,并且每个区域会比较平均。第二种生成的圆的圆心会更加靠近中间。

那么我们只要取一个中间区域的圆心数量,就可以判断是第一种还是第二种。

对于圆心 \((x,y)\)\(x\)\(y\) 都在 \([-9,9]\) 的概率为 \((\frac{19}{199})^2\) 。大约 \(0.01\)

参考代码

void Showball(){
   int n;
   cin>>n;
   int cnt=0;
   for(int i=0;i<n;i++){
    int x,y,r;
    cin>>x>>y>>r;
    if(-9<=x&&x<=9&&-9<=y&&y<=9) cnt++;
   }    
   if(cnt<=n*0.01) cout<<"bit-noob\n";
   else cout<<"buaa-noob\n";
}

J 又鸟之亦心 二分答案

题意:

有一对情侣他们的初始位置分别是 \(x\)\(y\) 。现在有 \(n\) 个出差任务需要去分配。第 \(i\) 个任务需要去 \(a_i\) 进行。

求出情侣两人相隔距离的最大值最小是多少?

思路:

最大值最小,经典的二分答案模型。

难点在于check()函数如何去写。

我们可以把能够完成的任务放到 \(set\) 中。然后遍历查询 \(set\) 中是否存在满足条件的情况。

不存在直接return false。对于如何判断是否存在满足条件的情况。我们可以把不满足情况

的都删去,然后判断 \(set\) 是否为空即可。

参考代码

void Showball(){
   int n;
   cin>>n;
   vector<int> a(n+2);
   for(int i=0;i<n+2;i++) cin>>a[i];

   auto check=[&](int d){
     set<int> s;
     s.insert(a[0]);
     for(int i=1;i<n+2;i++){
       if(s.empty()) return false;
       if(*s.rbegin()>a[i]+d) s.erase(s.upper_bound(a[i]+d),s.end());
       if(!s.empty()&&*s.begin()<a[i]-d) s.erase(s.begin(),s.lower_bound(a[i]-d));
       if(abs(a[i]-a[i-1])<=d) s.insert(a[i-1]); 
     }
     return !s.empty();
   };

   int l=abs(a[0]-a[1]),r=1e9;
   while(l<r){
      int mid=l+r>>1;
      if(check(mid)) r=mid;
      else l=mid+1;
   }
   cout<<l<<endl;
}

K 牛镇公务员考试 置换环

题意:

给你 \(n\) 道单选题, 每道题给你一个整数 \(a_i\) 和一个字符串 \(s_i\) (\(s_i\)是一个ABCDE的排列)。

则第 \(i\) 道题目的题面是:第 \(a_i\)题的答案是( )。

A:\(S_{i,1}\)

B:\(S_{i,2}\)

C:\(S_{i,3}\)

D:\(S_{i,4}\)

E:\(S_{i,5}\)

求出能够全部题目回答正确的答案有多少种?

思路:

看到这种 \(i\) 跳到 \(a_i\) 的就想到置换环,其中就会存在小循环(也就是成环的情况)。

找到环,然后删除环外的链。接着枚举所有可能选项,如果通过环一圈得到的选项和原来一致。

那么就是可行解。乘法原理统计即可。

参考代码

void Showball(){
   int n;
   cin>>n;
   vector<int> a(n);
   vector<string> s(n);
   for(int i=0;i<n;i++){
     cin>>a[i]>>s[i];
     a[i]--;
   }    
   vector<int> st(n);
   LL ans=1;
   for(int i=0;i<n;i++){
    int t=i;
    vector<int> c;
    while(!st[t]){
        st[t]=true;
        c.eb(t);
        t=a[t];
    }
    auto it=find(all(c),t);
    if(it!=c.end()){
        c.erase(c.begin(),it);//删除环之外的部分
        int res=0;
        for(char ch='A';ch<='E';ch++){
            char cur=ch;
            for(auto x:c){
                cur=s[x][cur-'A'];
            }
            res+=(cur==ch);
        }
        ans=ans*res%mod;
    }
   } 
   cout<<ans<<endl;
}

L 要有光 思维

题意:

一束光打在距离光源为 \(c\) 的宽为 \(2*w\) ,高为 \(h\) 的矩形,距离这个矩形 \(c\) 还有一面墙,问你这个光源照出的阴影面积最大值。

思路:

显然,这个面积是一个定值。根据相似三角形,最后阴影面积就是一个上底为 \(2*w\) ,下底为 \(4*w\) ,高为 \(c\) 的梯形。

参考代码

void Showball(){
   int c,d,h,w;
   cin>>c>>d>>h>>w;
   cout<<3*w*c<<endl;    
}

M 牛客老粉才知道的秘密 签到

题意:

题目一次最多只能展示六道题目,多余的题目需要进行左滑或右滑展示。

现在给你题目总数,问你出现在最左边的题目有几种情况。

思路:

很明显,答案是题数 \(n/6\) ,如果 \(n\% 6\neq0\) ,那么我们就可以从末尾倒着再取一次。

参考代码

void Showball(){
   int n;
   cin>>n;
   int ans=n/6;
   if(n%6!=0) ans*=2;
   cout<<ans<<endl;    
}
posted @ 2024-02-08 17:03  Showball  阅读(8)  评论(0编辑  收藏  举报