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\) 个相同的箱子里,求方案数,这就是第二类斯特林数。
参考代码:
//预处理组合数
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;
}