文章目录
前言
本篇博客是2024牛客算法冬令营第一次训练赛的全题解,包括题目:
- A.DFS搜索
- B.关鸡
- C.按闹分配
- D.数组成鸡
- E.本题又主要考察了贪心
- F.鸡数题
- G.why买外卖
- H.01背包,但是bit
- I .It’s bertrand paradox. Again!
- J.又鸟之亦心
- K.牛镇公务员考试
- L.要有光
- M.牛客老粉才知道的秘密
A.DFS搜索
1.题目描述
最近,fried-chicken完全学明白了DFS搜索(如上图所示)!于是学弟向他请教DFS搜索,fried-chicken热心的进行了讲解:
所谓DFS搜索,就是给定一个字符串s,问能否找到s的一个子序列,使得该子序列的值为 DFS 或 dfs。
请你分别判断字符串s中是否含有 DFS 子序列与 dfs 子序列。
子序列的定义:从原字符串中选择一些字符,将这些字符按照其在原串中的顺序拼接起来,得到的就是原字符串的一个子序列。例如:ABCDA的子序列可以为ACA、ABCDA、BA等等,但不能为ABE、CBA、AAD。
输入描述:
输入的第一行包括一个正整数T(1≤T≤100),表示测试用例的组数。
对每组测试用例,第一行是一个正整数n(1≤n≤50),表示输入字符串的长度。第二行是一个长度为n的字符串s,保证字符串中只含有英语小写字母与英语大写字母。
输出描述:
对于每组测试用例,输出空格分隔的两个数字,第一个数字表示是否含有 DFS 子序列,第二个数字表示是否含有 dfs 子序列。输出 1 表示含有,输出 0 表示不含有。
输入
5
6
dafasa
6
dDFfSs
6
sfdDSF
6
DFSDFS
3
dfs
输出
0 1
1 1
0 0
1 0
0 1
2.题解
- 循环遍历一遍,用两个标志flag1和flag2分别记录"DFS"和"dfs"情况即可
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin >> n;
while(n--)
{
int x;
cin >> x;
char a[x];
cin >> a;
int flag1=0;
int flag2=0;
for(int i=0;i<x;i++)
{
//判断DFS的存在
if(flag1==0)
{
if(a[i]=='D') flag1++;
}
else if(flag1==1)
{
if(a[i]=='F') flag1++;
}
else if(flag1==2)
{
if(a[i]=='S') flag1++;
}
//判断dfs的存在
if(flag2==0)
{
if(a[i]=='d') flag2++;
}
else if(flag2==1)
{
if(a[i]=='f') flag2++;
}
else if(flag2==2)
{
if(a[i]=='s') flag2++;
}
}
if(flag1==3) cout << 1 <<' ';
else cout << 0 << ' ';
if(flag2==3) cout << 1 << endl;
else cout << 0 << endl;
}
}
B.关鸡
1.题目描述
如图所示,在一条宽为2、长为2×109+1的管道中,有一只鸡和若干着火点,鸡可以上下左右移动一格、不能出管道上下边界、不能进入着火地点。
鸡初始在(1,0)处,现在给出若干个着火点的坐标,请求出为了不让鸡逃出管道(即到达管道最左端或最右端),最少需要添加多少个着火点。
输入描述:
输入第一行包括一个整数T(1≤T≤104),用例组数。
每组用例第一行包括一个整数n(0≤n≤105),着火点的个数。
随后的第iii行输入两个整数r,c(1≤r≤2,−109≤c≤109),表示着火点的坐标(r,c),坐标含义如题图所示。保证着火点不会为(1,0)。
保证所有数据的n之和Σn≤105,保证同一组用例输入中没有重复的着火点坐标。
输出描述:
对每组样例,输出一个整数,表示最少需要添加多少个着火点才能使得鸡逃不出管道。
输入
8
1
2 0
4
1 3
2 4
1 -2
2 -2
2
1 4
1 5
2
1 -2
2 4
1
1 -1
5
2 0
1 3
2 2
2 4
2 5
9
1 -5
2 2
2 -3
1 -4
1 -1
1 -3
1 -2
2 -4
2 3
0
输出
2
0
3
2
2
1
1
3
2.题解
- 这题需要分情况讨论
- 第一种不特殊考虑(1,-1)(2,0)(1,1)这三个特殊点,将左右分开计算,再将左右需要添加的点累加起来构成第一种情况的答案
- 第二种单独考虑这三个特殊点,3减去特殊点的个数就是第二种情况的答案
- 两种情况取最小值即可
- s.count({r^3,c+i})解释:看s中是否存在与(r,c)点可以构成一堵墙的点,r异或是取另一个行(因为二进制1异或3是2,2异或3是1),c加i(-1,1,0)是列左右平移或者不变
- 运用了set<pair<int,int>>s的相关知识:set会自动帮你按升序排列,先比较first,再比较second
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6;
void solve()
{
int n;
cin>>n;
set<pair<int,int>>s;
//先将左边和右边分开计算
int ans0=2,ans1=2;//初始为左边和右边都为空,需要每边添两个
//若左边或右边出现,那么只需添1个着火点
for(int i=0;i<n;i++)
{
int r,c;
cin>>r>>c;
s.insert({r,c});
//注意两个都要添加等于号
if(c<=0)
{
ans0=1;
}
if(c>=0)
{
ans1=1;
}
}
for(auto[r,c]:s)
{
for(int i=-1;i<=1;i++)
{
if(s.count({r^3,c+i})) //异或3是取另一个行,加i是列左右平移或者不变
{
if(c<0) ans0=0;
if(c>0) ans1=0;
}
}
}
//单独考虑三个特殊点
int ans=3-s.count({2,0})-s.count({1,-1})-s.count({1,1});
cout<<min(ans,ans0+ans1)<<'\n';
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T;
cin>>T;
while(T--)
{
solve();
}
return 0;
}
C.按闹分配
1.题目描述
办事大厅目前有n个人和一个办事窗口,每个人都要在这个窗口办事,第i个人办事所需时间为ti。
时刻0所有人都进入办事大厅,第i个人的不满意度Di定义为他的事情办完的那个时刻。定义所有人的总不满意度S=Σni=1Di。
办事处工作人员会合理安排办事顺序,使得总不满意度最小,记为Smin。
现在,很急的鸡来办事了,鸡可以在任意时刻要求工作人员放下手头的事情,立刻来处理鸡的事情,鸡的事情需要tc时间处理完成。假设鸡插队后其余n人的总不满意度最小值变为Sc,若Sc−Smin≤M,则工作人员将允许鸡的插队,否则工作人员将拒绝。M是工作人员的容忍限度。
现在,请你回答Q组询问,即当工作人员的容忍限度为M时,鸡最早能在哪个时刻办完事。
输入描述:
第一行输入三个正整数n,Q,tc(1≤n,Q≤105,1≤tc≤109),含义如题面所述。
第二行输入nnn个正整数ti(1≤ti≤106),表示第i个人办事所需时间。
接下来QQQ行,每行一个正整数M(0≤M≤1018),表示该组询问中工作人员的容忍度。
输出描述:
对于每组询问的容忍度,求出鸡在该容忍度下最早能在哪个时刻办完事。
输入
4 3 6
4 3 2 1
0
14
1000000000000
输出
16
9
6
2.题解
- 我们可以知道打断一个人和插入在他前面的效果是一样的,所以我们可以直接只考虑插入在他前面即可
- 需要先排序(为了降低时间复杂度,建议使用快排)
- 现在是给定了最大不满意度M,所以我们可以求出排在鸡后面的人数最多是M-tc,即可得到在鸡前面的人有哪些,将其时间累加并加上tc即可得到答案
#include<bits/stdc++.h>
using namespace std;
//快速排序
void quick_sort(int *q,int l,int r)
{
if(l>=r) return;
int x=q[l+r>>1],i=l-1,j=r+1;
while(i<j)
{
do i++;while(q[i]<x);
do j--;while(q[j]>x);
if(i<j) swap(q[i],q[j]);
}
quick_sort(q,l,j);
quick_sort(q,j+1,r);
}
int main()
{
int n,q,t;
cin >> n >> q >> t;
int a[n];
for(int i=0;i<n;i++) cin >> a[i];
quick_sort(a,0,n-1);
while(q--)
{
long s;
cin >> s;
long x= s/t;
long y=n-x;
long ans=0;
for(long i=0;i<y;i++) ans = ans+a[i];
ans = ans +t;
cout << ans <<endl;
}
}
D.数组成鸡
1.题目描述
小鸡有一个由整数组成的数组,小鸡可以对这个数组进行任意次(可以不进行)全数组每个数加一或全数组每个数减一的操作。
现在,小鸡想让你回答Q次询问,每次询问给出一个整数M,你需要回答任意次(可以不操作)操作后是否可以使得给定数组的乘积等于给出的整数M。
输入描述:
第一行输入两个正整数n,Q(2≤n≤105,1≤Q≤5×105),表示数组长度与询问次数。
第二行输入n个空格分隔的整数ai(−109≤ai≤109),表示数组中的元素。
接下来Q个空格分隔的整数M(−109≤M≤109),表示询问的数字。
输出描述:
对于每个M,请你输出一行"Yes"或"No"(不含引号)表示是否可以令全数组的乘积等于给定M。
示例1
输入
4 9
1 -1 3 5
-75 19305 123 1 0 15 -15 1919810 114514
输出
No
Yes
No
No
Yes
No
Yes
No
No
题解
- 注意到一个盲点:绝对值非1的最多30个(230>109超出了数据范围)
- n> 30:枚举哪个数变成1或-1(使得绝对值非1的最多30个才可,所以只用考虑枚举每个数变成1或者-1的情况,其他情况绝对值非1必然大于30个),然后把此时数组乘积算出来,在枚举前先判断一下这 个数变的话能不能保证变后绝对值非1的最多30个,所以真正要枚举的数不多
- n ≤ 30:从前往后从后往前各遍历一遍,防止漏情况(因为遍历范围是自己取的,从前往后从后往前遍历的范围不一致)
#include<bits/stdc++.h>
#define int long long
using namespace std;
void solve()
{
int n, m; cin >> n >> m;
vector<int>a(n); //存入各数
map<int,int>cnt; //计入各数的个数
set<int>ans; //存答案
ans.insert(0);
for(int i = 0; i < n; i ++)
{
cin >> a[i];
cnt[a[i]] += 1;
}
//n<30
if(n >= 30)
{
//一定要减少绝对值不等于1的数字个数。
for(auto [x, y]: cnt)
{
if(n - cnt[x] - cnt[x - 2] > 30) continue;
//该数变成1的情况
int mul = 1;
bool flag = true;
for(int i = 0; i < n; i ++)
{
mul = mul * (a[i] - (x - 1));
if(abs(mul) > 1e9)
{
flag = false;
break;
}
}
if(flag) ans.insert(mul);
//该数变成-1的情况
mul = 1, flag = true;
for(int i = 0; i < n; i ++)
{
mul = mul * (a[i] - (x + 1));
if(abs(mul) > 1e9)
{
flag = false;
break;
}
}
if(flag) ans.insert(mul);
}
}
//n<=30
else
{
//从前往后遍历
sort(a.begin(), a.end());
int tmp = a[0];
for(int i = 0; i < n; i ++)
{
a[i] -= tmp;
}
for(int i = -1e6; i <= 1e6; i ++)
{
int mul = 1;
bool flag = true;
for(int j = 0; j < n; j ++)
{
mul = mul * (a[j] + i);
if(abs(mul) > 1e9)
{
flag = false;
break;
}
}
if(flag) ans.insert(mul);
}
//从后往前遍历
reverse(a.begin(), a.end());
tmp = a[0];
for(int i = 0; i < n; i ++)
{
a[i] -= tmp;
}
for(int i = -1e6; i <= 1e6; i ++)
{
int mul = 1;
bool flag = true;
for(int j = 0; j < n; j ++)
{
mul = mul * (a[j] + i);
if(abs(mul) > 1e9)
{
flag = false;
break;
}
}
if(flag) ans.insert(mul);
}
}
while(m--)
{
int x; cin >> x;
if(ans.count(x))
{
cout << "Yes" << endl;
}
else
{
cout << "No" << endl;
}
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
t = 1;
while(t--)
solve();
}
E.本题又主要考察了贪心
1.题目描述
斗鸡联赛一共有n只鸡参加,第iii只鸡截至目前已经积了ai分。
接下来还有m场比赛要进行,第iii场比赛的对阵双方是编号为ui和vi的鸡。积分规则是:胜方加三分,败方不得分,若战平则双方各得一分。
请你计算在最好的情况下,我们的一号选手(炸鸡)能够排到第几名。
注意若有多鸡并列,则排名取并列的排名,且不影响随后的排名(例如两只鸡并列第二名,则都视为第二名,排名其后的下一只鸡视为第四名)。
输入描述:
输入第一行包括一个整数T(1≤T≤100),样例组数。
对于每组样例:
第一行输入两个整数n,m(2≤n≤10,1≤m≤10),含义如题面所述。
第二行输入n个整数ai(0≤ai≤100),表示第i只鸡当前已经有的积分。
接下来的m行,每行有两个正整数ui,vi(1≤ui,vi≤n,ui≠vi),表示第i场比赛的对阵双方。
输出描述:
对每组样例,输出一个整数表示一号选手最好的情况下能够排到第几名。
输入
3
4 3
2 4 5 8
1 2
1 4
2 4
3 1
3 1 1
2 3
6 6
1 2 3 4 5 6
2 3
2 3
3 4
4 5
5 6
6 1
输出
1
1
4
2.题解
- DFS:递归+回溯(因为观察一下,数据很小)
- 不要被题目的贪心蒙骗
#include <bits/stdc++.h>
using namespace std;
const int N=2e6+10;
struct node
{
int x,y;
}str[N];
int n,m;
int ans;
int dx[3]={0,1,3};
int dy[3]={3,1,0};
int a[N];
//一个dfs的递归
void dfs(int u)
{
if (u>=m)
{
int cnt=1;
for (int i=2;i<=n;i++)
{
if (a[i]>a[1]) cnt++;
}
ans=min(ans,cnt);
return ;
}
//dfs每一种情况
for (int i=0;i<3;i++)
{
int x=str[u].x,y=str[u].y;
a[x] +=dx[i];
a[y] +=dy[i];
dfs(u+1);
a[x] -=dx[i];//退层要还原
a[y] -=dy[i];
}
}
void solve()
{
cin>>n>>m;
for (int i=1;i<=n;i++) cin>>a[i];
for (int i=0;i<m;i++)
{
int x,y;
cin>>x>>y;
str[i]={x,y};
}
ans=n;
dfs(0);
cout<<ans<<endl;
}
signed main()
{
ios_base::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int T;
cin>>T;
while (T--) solve();
return 0;
}
F.鸡数题
1.题目描述
鸡想问有多少个长为m的数组a同时满足以下条件:
1、对于任意的i,ai>0;
2、对于任意的整数1≤i≤m−1,ai<ai+1;
3、a1∣a2∣…∣am−1∣am=2n−1(之中 | 为按位或操作);
4、对于任意的i≠j,满足ai&aj=0(之中&为按位与操作)。
你的答案需要对109+7取模。
输入描述:
输入两个正整数n,m(1≤n,m≤105),含义如题所述。
输出描述:
输出一个答案,表示满足条件的数组个数,你的答案需要对109+7取模。
输入
4 3
输出
6
输入
1000 400
输出
376905797
2.题解
- 转化问题为:将n个不同的小球装入m个相同的箱子中,问有多少种不同的情况,即第二类斯特林数,公式:{n,m}=(1/m!)*(k从0累加到m)(-1)^k * C(m,k)*(m-k)^n
- 第一点说明了盒子里要么没有小球有么有小球(可多个)
- 第二点说明了盒子的相同,因为最后都会按照升序排列,所以相当于盒子是相同的
- 第四点说明了小球是不同的,所以ai&aj=0,相当于每个小球是二进制上的一个不同的位为1的数,其他位都是0
- 第三点说明了有n个小球
- 本题的思想就是将位运算的问题转化为斯特林数实例问题
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=1e9+7;
const int N=1e5+10;
ll fact[N],infact[N];
//快速幂求a^k mod m
ll qmi(ll a, ll k, ll m)
{
a%=m;
ll res = 1 ;
while (k)
{
if (k&1) res = res * a % m; //&1判断是否是奇数
a = a * a % m;
k >>= 1; //>>1是除以2
}
return res;
}
//预处理阶乘取模的余数和阶乘取模余数的逆元(费马小定理求逆元)
void init(int n=1e5)
{
fact[0] = infact[0] = 1;
for (int i = 1; i <= n; i ++ )
{
fact[i] = fact[i - 1] * i % mod;
infact[i] = infact[i - 1] * qmi(i, mod - 2, mod) % mod;
}
}
//计算组合数
ll C(int a,int b)
{
return fact[a] * infact[a-b] %mod * infact[b] %mod;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
init();
int n,m;
cin >> n >> m;
ll ans=0;
for(int i=0;i<=m;i++) ans=(ans+(i%2?-1:1)*C(m,i)*qmi(m-i,n,mod)%mod+mod)%mod; //+mod为了保证大于0
cout << ans*qmi(fact[m],mod-2,mod)%mod << endl;
return 0;
}
G.why买外卖
1.题目描述
鸡很饿,鸡要吃外卖,今天点份炸鸡外卖!
鸡使用的外卖程序有若干个满减优惠,第i个优惠可以表示为"满ai元减bi元",多个满减优惠可以叠加。
满减的具体结算流程是:假设鸡购买的食物原价共为x元,则所有满足x≥ai的满减优惠都可以一起同时被使用,优惠后价格记为y,则鸡只要支付y元就可以了(若y≤0则不需要支付)。
现在,鸡的手机里一共只有m元钱,鸡想知道,他所购买的食物原价x最多为多少。
输入描述:
输入第一行包括一个整数T(1≤T≤104)样例组数。
对于每组样例,第一行输入两个整数n,m(1≤n≤105,1≤m≤109),含义如题面所述。接下来的n行,每行输入两个正整数ai,bi(1≤ai,bi≤109),表示一个满减优惠。
保证所有样例的Σn≤105。
输出描述:
对每组用例,输出一个整数,表示鸡能购买的食物原价x最多为多少。
输入
4
1 10
100 80
2 10
30 10
100 90
3 10
100 30
100 30
100 30
2 10
21 10
1000 1
输出
10
110
100
10
2.题解
- 易得公式:原价=m+优惠价格
- 用满减的需求金额划分区间,判断每个区间是否成立即可,可以从大往小遍历,一旦成功直接break,降低时间复杂度
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
struct Range
{
int x,y;
//按照满多少元的大小按照升序排序
bool operator < (const Range &W) const
{
return x < W.x;
}
}range[N];
int main()
{
int s;
cin >> s;
while(s--)
{
int n,m;
cin >> n >> m;
for(int i=0;i < n; i++ )
{
int x,y;
cin >> x >> y;
range[i] = {x,y};
}
//按照满多少元的大小按照升序排序
sort(range,range + n);
long yuan = 0;
long jian = 0;
for(int i=0;i<n;i++) jian = jian + range[i].y;
for(int i=n-1;i>=0;i--)
{
if(i!=n-1&&range[i+1].x-1-jian>=m&&range[i].x-jian<=m)
{
yuan = m + jian;
break;
}
if(i==n-1&&range[i].x-jian<=m)
{
yuan = m + jian;
break;
}
jian = jian - range[i].y;
}
if(yuan == 0) cout << m <<endl;
else cout << yuan <<endl;
}
}
H.01背包,但是bit
1.问题描述
共有n件物品,每件物品有价值vi与重量wi两个属性。但特别地,所选物品的总重量并不是每件物品的重量和,而是所有所选物品的重量进行按位或运算的结果。
请你计算,在所选物品总重量不超过m的情况下,所选物品的最大价值之和是多少(价值之和正常定义为所选物品价值的加和)。
输入描述:
第一行包括一个正整数T(1≤T≤104),表示用例组数。
每组用例的第一行包括两个整数n,m(1≤n≤105,0≤m≤108),含义如题面所述。接下来的n行,每行包括两个整数vi,wi(0≤vi≤108,0≤wi≤108),含义如题面所述。
保证所有用例的Σn≤105。
输出描述:
对每组样例,输出一个正整数,表示所选物品的最大价值之和。
输入
3
4 11
1 8
1 4
1 5
1 1
4 11
5 8
1 4
1 5
1 1
4 0
2 0
0 0
3 0
4 1
输出
3
6
5
2.题解
- 记所选物品重量或起来是c,枚举m里是1的某个bit,强制c里该位为0,则该位将m分成了前 后两部分
- 对于前面的那部分位(更高位),要求所选的物品这些位必须是m的子集(即m对应位是1 才能选)
- 对于后面的那部分位(更低位),没有任何限制
- 二进制中如何才能达到这样的效果呢???我们可以每次-1即可,比如m二进制1001000减去1即为1000111,使得物品重量和1000111相与,结果依然是物品重量即可,也就是最后一位1的高位部分为必须为m子集,最后一位1的低位部分由于全是1,相与必然是重量该部分原本的值,则可以为任意,由此遍历
- 那么如何遍历呢???每次去掉去后一位1(m&-1),再进行操作(-1)即可
#include<bits/stdc++.h>
using namespace std;
long long v[100005],w[100005];
long long n,m;
long long get(int x)
{
long long sum = 0;
for(int i = 1;i <= n;i++)
{
if((x & w[i]) == w[i]) sum += v[i];//位与小的数等于它本身才行
}
return sum;
}
void solve()
{
cin >> n >> m;
long long ans = 0;
for(int i = 1;i <= n;i++) cin >> v[i] >> w[i];
ans = get(m);//刚好相等的情况
//i -= (i & -i)每次去掉最后一位1,遍历每种1是0的情况
//get(i-1)就相当于不考虑低位了,高位子集,低位无所谓
for(int i = m; i ;i -= (i & -i)) ans = max(ans,get(i-1));
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T;
cin >> T;
while(T--)
{
solve();
}
return 0;
}
I .It’s bertrand paradox. Again!
1.题解
fried-chicken为学生们布置了一项作业:
“随机生成105个平面上的圆,使得这些圆满足:圆心坐标为整点、圆半径为整数。所有圆上的每一个点都在−100≤x≤100和−100≤y≤100所确定的平面区域内(可以在边界上),允许有重复的圆,但要求生成的圆是随机的。”
但由于作业要求中"随机生成"这一点说的含糊不清,fried-chicken的两个学生,bit-noob和buaa-noob采用了以下两种不同的生成方法。
bit-noob的方法:
1、随机等概率地从开区间(−100,100)生成两个整数x,y。
2、随机等概率地从闭区间[1,100]中生成一个r。
3、判断(x,y)为圆心、r为半径的圆是否满足要求,若不满足,返回步骤2重新生成r,若满足,则将该圆加入到结果中。
buaa-noob的方法:
1、随机等概率地从开区间(−100,100)生成两个整数x,y,随机等概率地从闭区间[1,100]中生成一个r。
2、判断(x,y)为圆心、r为半径的圆是否满足要求,若不满足,返回步骤1重新生成x,y,r,若满足,则将该圆加入到结果中。
于是,两人使用各自的方法,各自生成了105个圆,将作业交了上去。
现在,给出一份作业,请你判断这份作业是哪位同学的。本题共有 50 组数据。
输入描述:
输入第一行是一个整数n(n=105),表示生成的圆的数量。
接下来第i行是三个整数xi,yi,ri(−100<xi,yi<100,0<ri≤100),表示这位同学生成的第i个圆。
输出描述:
输出"bit-noob"或"buaa-noob"(不含引号)表示这份作业来自哪位同学。
输入
10
1 55 1
-83 48 1
-70 -86 11
-74 12 18
39 46 18
1 78 11
78 -12 3
-86 -93 7
-56 8 26
-26 -90 4
输出
bit-noob
说明
该样例只为说明输入输出格式,实际的所有测试数据中都会严格按照输入描述中的要求、并不存在 n 为 10 的情况。
2.题解
- 我们需要注意到bit-noob的方法生成坐标是均匀的,buaa-noob是不均匀的,那么我们可以取一个区域,看落点概率,并且设立一个阈值即可
#include<bits/stdc++.h>
using namespace std;
int main()
{
ios_base::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int n;
cin >> n;
//bit-noob方法的均匀落点概率(理想情况)
double biaozhun = 141*141*1.0/(201*201);
//计算真正的落点概率
int sum=0,su=0;
while(n--)
{
int x,y,r;
cin >> x >> y >> r;
sum++;
if(x<=70&&x>=-70&&y>=-70&&y<=70) su++;
}
double ans=su*1.0/sum;
//相差不超过0.2是我们设计的阈值
if(fabs(ans-biaozhun)<0.2) cout << "bit-noob" << endl;
else cout << "buaa-noob" << endl;
}
J.又鸟之亦心
1.题目描述
小又和小鸟是生活在一维世界的一对恋人,在一维世界中,每个位置坐标可以通过一个整数表示,两个位置的距离定义为两个坐标之差的绝对值。
小又和小鸟在同一家公司的两个分部工作,小又所在分部位于位置x,小鸟所在分部位于位置y。
接下来的一年中,公司依次有n个外派任务(按时间顺序给出),第i个外派任务要求一个员工外派调到位于ai位置的分部工作直至另行通知为止。作为公司唯二的两个空闲员工,小又和小鸟必须有一人前去该分部工作。
现在,请你帮忙决定每个任务是要外派小又还是小鸟,怎样才能让这对恋人相距尽可能近。具体来说,请求出在最优安排下,两人全过程中距离的最大值最小为多少。
输入描述:
第一行是三个整数n,x,y(1≤n≤105,0≤x,y≤109),含义如题所述。
第二行输入n个整数,第i个整数表示第i个外派任务的位置ai(0≤ai≤109)。
特别地,保证x,y,ai之间不存在相同的两个数字。
输出描述:
输出一个整数,即两人全过程中距离的最大值最小为多少。
输入
2 0 10
5 6
输出
10
输入
1 4 5
2
输出
2
输入
3 2 1
3 4 5
输出
1
2.题解
- 找一定的范围,用二分不断逼近答案
- 每次思路是,固定一个点,找另一个点,依次轮回,看是否能遍历完成
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
#define endl '\n'
void solve(){
int n, x, y;
cin >> n >> x >> y;
vector<int> a(n);
for (int i = 0; i < n; i++)
{
cin >> a[i];
}
/*auto check = [&](int d) 是一个lambda表达式。
它接受一个整数参数d,并且使用捕获列表[&]来捕获当前作用域中的所有变量(包括引用)。
这个lambda表达式可以用于定义一个函数对象,可以在需要的地方调用它。
当调用check(d)时,它会执行lambda表达式中的代码。*/
//不断轮回x,y不动,反复遍历看是否可以遍历完所有点,就是结果非空
auto check = [&](int d)
{
int lst = y;//lst相当于每一轮不动的点
set<int> S;
if (abs(x - y) <= d) S.insert(x);
for (auto x : a)
{
if (!S.empty() && abs(x - lst) <= d) S.insert(lst);
//S.begin()和S.rbegin()都是迭代器,需要加*才是值
while (!S.empty() && *S.begin() < x - d) S.erase(*S.begin());
while (!S.empty() && *S.rbegin() > x + d) S.erase(*S.rbegin());
lst = x;
}
return !S.empty();
};
//用二分法不断趋近答案
int l = 0, r = 1e9;
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
cout << l << endl;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
solve();
return 0;
}
K.牛镇公务员考试
1.题目描述
在刚刚结束的miaohunt 2023中,炸鸡块君独自一人花了两个多小时、写了200行代码、写出了《喵镇公务员考试》一题,炸鸡块君也不禁疑惑,怎么我找了四个acmer组队,还是要自己写代码?
好心的炸鸡块君既想让大家也体会到这题的痛苦,又不想把大家折磨到寒假营退款,于是炸鸡块君对原有的规则进行了简化:
一份有n道单选题的试卷,每道题可以用一个整数ai和一个长为5的字符串si表示,之中字符串仅含有大写的ABCDE字母、且每个字母出现恰好一次(即si是ABCDE的一个排列),si,j表示第si的第j个字符。
则第i道选择题的题面为:
第i题:第ai题的答案是()。
A. si,1
B. si,2
C. si,3
D. si,4
E. si,5
现在,请你回答,正确完成全部n道题的答案有多少种。答案请对998244353取模。
输入描述:
输入第一行包括一个整数n(1≤n≤105),题目数量。
接下来每一行包括一个整数ai(1≤ai≤n)和一个字符串si,含义如题面所述。保证si是大写字母ABCDE的一个排列。
输出描述:
输出一个整数,表示正确完成全部n道题的答案有多少种,答案请对998244353取模。
输入
3
1 ABCDE
2 AECDB
3 BCDEA
输出
0
输入
5
1 AEDCB
5 ACBDE
4 BCDEA
5 EDCBA
3 CEBDA
输出
1
输入
2
2 ABCDE
2 EDCBA
输出
1
题解
- 理解题意:这题的意思是第i题的答案通过si的对应可以找到第ai题的答案,因此每个i其实都指向一个ai,构成了基环内向树森林
- 首先所有连到环的链可以直接无视,它们不影响答案,这是因为,这个链所连接的环上 那个点确定答案后、链上每个点的答案可以由此反向传播依次得到(至于为何每个点答 案唯一?这是因为该点走到环的路径上所有排列复合得到的仍是排列),因此,环上点 确定方案、则链也随之确定唯一一种方案,所以可以忽略链
- 对于环,我们可以通过假设环入点的值,遍历一遍ABCDE,绕环一圈再看入点是否满足开始的假设,满足则是一种正确的情况
#include <bits/stdc++.h>
#define ll long long
#define pb push_back
using namespace std;
const int N=3e5+7;
const int p=998244353;
int tt; //样例数
int n; //题目数
int a[N],top=0; //表示i指向ai
bool b[N]; //是否遍历过,相当于一个标志
char s[N][7]; //存每个字符串
void solve()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
scanf("%s",s[i]+1);
}
ll ans=1;
for(int i=1;i<=n;i++)
{
int now=i;
vector<int> v;//存入遍历的情况
while(b[now]==0)//保证没有遍历过
{
b[now]=1;
v.pb(now);
now=a[now];
}//遍历后now将指向一个基环内向树的入点
auto it=find(v.begin(),v.end(),now);
//假设值进行遍历,绕一圈判断入点的假设是否正确
int sum=0;
for(int j=1;j<=5;j++)
{
char tmp=j+'A'-1;
char ii;
for(auto to:v)
{
if(to==now)ii=tmp; //入循环时,ii存入了环第一个数的答案
tmp=s[to][tmp-'A'+1];
}//循环结束temp刚好指向环第一个数的答案
if(ii==tmp)sum++;
}
ans=(ans*sum)%p;
}
printf("%lld\n",ans);
}
signed main()
{
tt=1;
while(tt--)
{
solve();
}
return 0;
}
L.要有光
1.题目描述
神说要有光,于是就有了本题。
如图所示,一个漆黑的世界可以用如图的三维坐标系来表示,XoY平面表示该世界的地面。
坐标系由以下几个组成部分:
1、一面无限宽无限高的白墙S,可以用如下公式表示:
x=-c&&z>=0
2、一面宽为2w高为h的绿墙W(厚度忽略不计),可以用如下公式表示:
x=0&&-w<=y<=w&&0<=z<=h
3、这个世界唯一的点光源L(体积忽略不计),点光源必须在图中的黑色线段上,黑色线段可以用如下公式表示:
x=c&&y=0&&0<=z<=d
神的目的是照亮这片大地,但同时也要防止人类给点阳光就灿烂。因此,神可以决定放置点光源L的位置(神必须放置点光源),使得未被照亮的土地面积尽可能大,请你输出未被照亮的土地面积的最大值。
说明:对于地面上的一点,若其与点光源L的连线接触到了绿墙W,则该点未被照亮。
注意:神只想最大化未被照亮的土地面积最大值,墙面S上未被照亮的面积不算做土地面积。且我们不计算墙S背后(x轴负方向)的未被照亮的土地面积。
输入描述:
输入包含T(1≤T≤104)组测试用例。
每组测试用例包含四个整数c,d,h,w(1≤c,d,h,w≤104),含义如题面所述。
输出描述:
输出一个浮点数,表示你的答案。当你的答案的绝对误差或相对误差小于10−4时,答案被视为正确。
输入
1
2 4 2 1
输出
6
2.题解
- 画画图,用几何知识可知,答案即为3cw
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin >> n;
while(n--)
{
int c,d,h,w;
float ans;
cin >> c >> d >> h >> w;
ans=3.0*w*c;
cout << ans<<endl;
}
}
M.牛客老粉才知道的秘密
1.题目描述
现在,在本次比赛的主页点击"排名",您就会看到本场比赛的榜单,可以看到,榜单中直接列出了本场比赛的所有题目。
现在,作为牛客老粉,炸鸡想用这道题给大家科普一下牛客以前榜单的愚蠢之处:
牛客以前的榜单并不是现在这样,而是至多同时只显示六道题目。同时榜单上还有"向左"按钮与"向右"按钮来切换显示的题目。以"向右"按钮为例,点击一次该按钮会显示接下来的六道题,特别的,如果接下来的六道题超出了总题数,则会将最后一题放到当前显示的最右侧。"向左"按钮同理。
现在,你需要回答,对于n道题的一场比赛,显示的六道题目中最左侧的题目一共有几种可能取值。
以下面共n=14道题的情况为例:
初始时,显示了 A 到 F;点击一次"向右",显示了 G 到 L;再点击一次"向右",此时由于剩余题数不足六题,显示的六道题是 I 到 N;此时不能继续点击"向右",点击一次"向左",显示的六道题是 C 到 H;再点击一次"向左",由于剩余题数不足六题,显示的六道题是 A 到 F。
上述过程中,显示的六道题中,最左侧的题目编号分别是 A、G、I、C、A,因此答案为 4。
输入描述:
输入的第一行包括一个正整数T(1≤T≤105),表示测试用例的组数。
每组测试用例输入一个正整数n(6≤n≤109),表示本场比赛的总题目数。
输出描述:
对每组测试用例,输出一个整数,表示显示的六道题目中最左侧的题目一共有几种可能取值。
输入
2
14
6
输出
4
1
2.题解
- n只用考虑是或不是6的倍数两种情况即可
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin >> n;
while(n--)
{
int m;
cin >> m;
int sum=0;
sum = m/6;
if(m%6!=0) sum = sum*2;
cout << sum <<endl;
}
}