牛客竞赛_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ
注:本题解只涉及赛时思路和赛后补题收获,人菜佬勿喷
赛时六题蒟蒻【ABCGLM】 补题:【EFI】
A.DFS搜索
A-DFS搜索_2024牛客寒假算法基础集训营1 (nowcoder.com)
给定一个长度为n的字符串s,求子序列中是否含有 “DFS",“dfs" 子串
1≤n≤50
分析:
n的范围很小,赛时第一个想法直接暴力枚举,时间复杂度
代码:
(史山
void solve(){
int n;
cin >> n;
string s;
cin >> s;
int ok = 0, flag = 0;
for(int i = 0; i < n; i++){
if(s[i] == 'd'){
for(int j = i + 1; j < n; j++){
if(s[j] == 'f'){
for(int k = j + 1; k < n; k++){
if(s[k] == 's'){
ok = 1;
break;
}
}
if(ok)
break;
}
}
}
if(s[i] == 'D'){
for(int j = i + 1; j < n; j++){
if(s[j] == 'F'){
for(int k = j + 1; k < n; k++){
if(s[k] == 'S'){
flag = 1;
break;
}
}
if(flag)
break;
}
}
}
}
cout << flag << " " << ok << endl;
}
B.关鸡
B-关鸡_2024牛客寒假算法基础集训营1 (nowcoder.com)
在一条宽为 2 ,长为 的管道中,有一只鸡和若干个着火点。鸡可以在管道内上下左右移动。
鸡初始在 (1,0) 处,现在给定 n 个着火点的坐标,求出为了不让鸡逃出管道,最少要添加多少个着火点。
求出需要添加多少个着火点才不能让鸡逃出去。
分析:
以原点(鸡最初的位置)为中点,将管道分为两段(负数为左侧,正数右侧。文章下以左右侧称)
对于同侧的每两个着火点,只要它们能组成一条竖线或者左右两条对角线(下标相邻)即可将此侧封住
同时考虑特殊情况将(1,1)(1,-1)(2,0)设置为着火点也可将鸡包围。
下面是赛时屎山代码,分类讨论:
代码:
void solve(){
int n;
cin >> n;
map<ll, int>mp;//map存该点有几个着火点(1代表在1位置有着火点,2代表在2位置有着火点,3代表位置1和位置2都有着火点)
int r, c[N] = {0};
for(int i = 1; i <= n; i++){
cin >> r >> c[i];
if(mp[c[i]])
mp[c[i]] = 3;
else
mp[c[i]] = r;
}
sort(c + 1, c + n + 1);
int ok = 0, flag = 0, l = 0, f = 0;//ok代表左侧是否封住,l代表左侧是否有着火点,flag代表右侧是否封住,f代表右侧是否有着火点
for(int i = 1; i <= n; i++){
if(c[i] < 0){//检验左侧,两个点的map值相加或者自身map值等于3即可封住
l = 1;
if(c[i] == -1){//-1这个点只需看-2这个点能否与-1这个点封住以及自身能否构成竖线
if(mp[c[i]] + mp[c[i] - 1] == 3 || mp[c[i]] == 3){
ok = 1;
}
}
else{//其余点判断能否与左右两点构成对角线或者自身能否构成竖线
if(mp[c[i]] + mp[c[i] - 1] == 3 || mp[c[i]] + mp[c[i] + 1] == 3 || mp[c[i]] == 3){
ok = 1;
}
}
}
else if(c[i] > 0){//检验右侧,两个点的map值相加或者自身map值等于3即可封住
f = 1;
if(c[i] == 1){1这个点只需看2这个点能否与1这个点封住
if(mp[c[i]] + mp[c[i] + 1] == 3 || mp[c[i]] == 3){
flag = 1;
}
}
else{//其余点判断能否与左右两点构成对角线或者自身能否构成竖线
if(mp[c[i]] + mp[c[i] - 1] == 3 || mp[c[i]] + mp[c[i] + 1] == 3 || mp[c[i]] == 3){
flag = 1;
}
}
}
}
//下面分类讨论每一种情况
if(ok && flag)//两边都已经封住则输出0
cout << 0 << endl;
else if(ok && !flag){//左侧封住只需判断右侧
if(mp[0]){//特判0这个位置是否有着火点
if(mp[1] == 1){//如果点1在1位置有着火点那么不需要加着火点
cout << 0 << endl;
}
else{//点1在1位置没有着火点则只需要在(1,-1)添加着火点即可
cout << 1 << endl;
}
}
else{//0这个位置没有着火点
if(mp[1] == 1){//如果点1的1位置有着火点,则只需在(2,0)添加
cout << 1 << endl;
}
else{//点1的1位置没有
if(f)//如果右侧有着火点,则只需在此着火点的位置添加一个构成一条竖线即可
cout << 1 << endl;
else//没有那就添加两个构成竖线或者对角线
cout << 2 << endl;
}
}
}
else if(!ok && flag){//右侧封住只需判断左侧,与上钟情况同理
if(mp[0]){
if(mp[-1] == 1){
cout << 0 << endl;
}
else{
cout << 1 << endl;
}
}
else{
if(mp[-1] == 1){
cout << 1 << endl;
}
else{
if(l)
cout << 1 << endl;
else
cout << 2 << endl;
}
}
}
else{//两边都没有封住
if(mp[0]){//如果0这个位置有着火点
if(mp[1] == 1){//点1在1位置有着火点
if(mp[-1] == 1)//点-1在1位置有着火点则已经封住
cout << 0 << endl;
else//没有则只需在(1,-1)添加
cout << 1 << endl;
}
else{//点1在1位置没有
if(mp[-1] == 1)//-1在1位置有,则只需在(1,1)添加
cout << 1 << endl;
else//没有则在(1,-1)(1,1)添加
cout << 2 << endl;
}
}
else{//0没有
if(mp[1] == 1){//点1在1位置有着火点
if(mp[-1] == 1)//点-1在1位置有着火点则只需在(2,0)添加
cout << 1 << endl;
else//没有则只需在(2,0)(1,-1)添加
cout << 2 << endl;
}
else{//点1在1位置没有
if(mp[-1] == 1)//-1在1位置有则只需在(2,0)(1,1)添加
cout << 2 << endl;
else//没有的话只需在(2,0)(1,1)(-1,1)这种情况与两边都构成竖线或者对角线取最小值即可
cout << min(3, 2 - l + 2 - f) << endl;
}
}
}
}
C.按闹分配
C-按闹分配_2024牛客寒假算法基础集训营1 (nowcoder.com)
办事大厅目前有 n 个人和一个办事窗口,每个人都要在这个窗口办事,第 i 个人办事所需时间为 。
时刻 0 所有人都进入办事大厅,第 i 个人的不满意度 定义为他的事情办完的那个时刻。定义所有人的总不满意度
。
办事处工作人员会合理安排办事顺序,使得总不满意度最小,记为 。
现在,很急的鸡来办事了,鸡可以在任意时刻要求工作人员放下手头的事情,立刻来处理鸡的事情,鸡的事情需要时间处理完成。假设鸡插队后其余 n 人的总不满意度最小值变为
,若
,则工作人员将允许鸡的插队,否则工作人员将拒绝。 M 是工作人员的容忍限度。
现在,请你回答 Q 组询问,即当工作人员的容忍限度为 M时,鸡最早能在哪个时刻办完事
分析:
赛时读完题第一个想法就是前缀和+二分
不难发现最小的容忍度是当每个人办事时间从小到大排序时,然后计算出该最小容忍度。
先求出前缀和,最小容忍度即是前缀和的总和。
然后二分插队的时间,如果当前这个时间插队使容忍度 ≤ M ,则该时间是允许的
这个题 M 数据范围很大,前缀和数组也会爆 int,需要注意开 longlong
代码:
ll n, q, t;
vector<ll>a, b;
bool check(ll x, ll m, ll sum){
ll pos = upper_bound(b.begin() + 1, b.end(), x) - b.begin();
if((n - pos + 1) * t <= m)
return 1;
return 0;
}
void solve(){
cin >> n >> q >> t;
a.resize(n + 1);
b.resize(n + 1);
ll sum = 0;
for(int i = 1; i <= n; i++){
cin >> a[i];
}
sort(a.begin() + 1, a.end());
for(int i = 1; i <= n; i++){
b[i] = b[i - 1] + a[i];
sum += b[i];
}
while(q--){
ll m;
cin >> m;
ll l = 0, r = sum + 1, ans = 0;
while(l < r){
ll mid = l + r >> 1;
if(check(mid, m, sum)){
ans = 1;
r = mid;
}
else
l = mid + 1;
}
if(ans)
cout << l + t << endl;
else
cout << sum << endl;
}
}
G.why买外卖
G-why买外卖_2024牛客寒假算法基础集训营1 (nowcoder.com)
鸡很饿,鸡要吃外卖,今天点份炸鸡外卖!
鸡使用的外卖程序有若干个满减优惠,第 i 个优惠可以表示为"满 元减
元",多个满减优惠可以叠加。
满减的具体结算流程是:假设鸡购买的食物原价共为 x 元,则所有满足 的满减优惠都可以一起同时被使用,优惠后价格记为 y ,则鸡只要支付 y 元就可以了(若 y≤0 则不需要支付)。
现在,鸡的手机里一共只有m 元钱,鸡想知道,他所购买的食物原价 x 最多为多少。
分析:
这道题的关键是所有满足 的满减优惠可以叠加。
只需按照 的大小从小到大将
和
进行结构体排序,然后遍历一遍如果当前的
加上累加的优惠 ≥ 当前的
,那就更新最大值。
同时注意数据范围,需要开 longlong
代码:
struct st{
ll a, b;
}s[N];
bool operator<(const st &l, const st &r){
return l.a < r.a;
}
void solve(){
ll n, m;
cin >> n >> m;
ll ans = -1, sum = 0;
for(int i = 0; i < n; i++){
cin >> s[i].a >> s[i].b;
}
sort(s, s + n);
sum = m;
for(int i = 0; i < n; i++){
if(sum + s[i].b >= s[i].a){
ans = max(sum + s[i].b, ans);
}
sum += s[i].b;
}
cout << max(ans, m) << endl;
}
L.要有光
L-要有光_2024牛客寒假算法基础集训营1 (nowcoder.com)
神说要有光,于是就有了本题。
如图所示,一个漆黑的世界可以用如图的三维坐标系来表示,XoY平面表示该世界的地面。
坐标系由以下几个组成部分:
1、一面无限宽无限高的白墙S ,可以用如下公式表示:
2、一面宽为 2w 高为 ℎ 的绿墙 W (厚度忽略不计),可以用如下公式表示:
3、这个世界唯一的点光源 L(体积忽略不计),点光源必须在图中的黑色线段上,黑色线段可以用如下公式表示:
神的目的是照亮这片大地,但同时也要防止人类给点阳光就灿烂。因此,神可以决定放置点光源L的位置(神必须放置点光源),使得未被照亮的土地面积尽可能大,请你输出未被照亮的土地面积的最大值。
说明:对于地面上的一点,若其与点光源L的连线接触到了绿墙W,则该点未被照亮。
注意:神只想最大化未被照亮的土地面积最大值,墙面S上未被照亮的面积不算做土地面积。且我们不计算墙S背后(x轴负方向)的未被照亮的土地面积。
分析:
很明显的一道数学题(高中数学?
可以取临界值 2ℎ ,正好可以打到墙的最底端,根据相似三角形可以算出阴影梯形面积即为
代码:
void solve(){
int c, d, h, w;
cin >> c >> d >> h >> w;
db ans = (6 * w * c) * 1.0 / 2.0;
printf("%.12lf\n", ans);
}
M.牛客老粉才知道的秘密
M-牛客老粉才知道的秘密_2024牛客寒假算法基础集训营1 (nowcoder.com)
现在,在本次比赛的主页点击"排名",您就会看到本场比赛的榜单,可以看到,榜单中直接列出了本场比赛的所有题目。
现在,作为牛客老粉,炸鸡想用这道题给大家科普一下牛客以前榜单的愚蠢之处:
牛客以前的榜单并不是现在这样,而是至多同时只显示六道题目。同时榜单上还有"向左"按钮与"向右"按钮来切换显示的题目。以"向右"按钮为例,点击一次该按钮会显示接下来的六道题,特别的,如果接下来的六道题超出了总题数,则会将最后一题放到当前显示的最右侧。"向左"按钮同理。
现在,你需要回答,对于n 道题的一场比赛,显示的六道题目中最左侧的题目一共有几种可能取值。
以下面共 n=14 道题的情况为例:
初始时,显示了 A 到 F;点击一次"向右",显示了 G 到 L;再点击一次"向右",此时由于剩余题数不足六题,显示的六道题是 I 到 N;此时不能继续点击"向右",点击一次"向左",显示的六道题是 C 到 H;再点击一次"向左",由于剩余题数不足六题,显示的六道题是 A 到 F。
上述过程中,显示的六道题中,最左侧的题目编号分别是 A、G、I、C、A,因此答案为 4。
分析:
签到题,只需判断 n 是否能被 6 整除即可
代码:
void solve(){
int n;
cin >> n;
int ans = 0;
ans += n / 6;
if(n % 6){
ans += n / 6;
}
cout << ans << endl;
}
E.本题又主要考察了贪心
E-本题又主要考察了贪心_2024牛客寒假算法基础集训营1 (nowcoder.com)
斗鸡联赛一共有 n 只鸡参加,第 i 只鸡截至目前已经积了 分。
接下来还有 m 场比赛要进行,第 i 场比赛的对阵双方是编号为 和
的鸡。积分规则是:胜方加三分,败方不得分,若战平则双方各得一分。
请你计算在最好的情况下,我们的一号选手(炸鸡)能够排到第几名。
注意若有多鸡并列,则排名取并列的排名,且不影响随后的排名(例如两只鸡并列第二名,则都视为第二名,排名其后的下一只鸡视为第四名)。
分析:
赛时贪心了五个小时没贪出来,是蒟蒻无疑了,想到暴力但是没去做。
这个数据范围很小直接 dfs就好了。
代码:
int a[105];
int n, m;
int u[105], v[105];
int ans;
void dfs(int k){
if(k > m){//遍历完成寻找名次
int pos = 1;
for(int i = 1; i <= n; i++){
if(a[i] > a[1])
pos++;
}
ans = min(ans, pos);
return;
}
a[u[k]] += 3;//u[k]赢
dfs(k + 1);
a[u[k]] -= 3;//回溯
a[v[k]] += 3;//v[k]赢
dfs(k + 1);
a[v[k]] -= 3;//回溯
a[u[k]]++;//平局
a[v[k]]++;
dfs(k + 1);
a[u[k]]--;//回溯
a[v[k]]--;
}
void solve(){
cin >> n >> m;
for(int i = 1; i <= n; i++)
cin >> a[i];
for(int i = 1; i <= m; i++)
cin >> u[i] >> v[i];
ans = N;
dfs(1);
cout << ans << endl;
}
I.It's bertrand paradox. Again!
I-It's bertrand paradox. Again!_2024牛客寒假算法基础集训营1 (nowcoder.com)
fried-chicken为学生们布置了一项作业:
"随机生成 个平面上的圆,使得这些圆满足:圆心坐标为整点、圆半径为整数。所有圆上的每一个点都在 和−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 组数据。
分析:
赛时一直在贪e,没开这个题,赛后发现还是不难的(
不难发现bit-noob的图像是均匀分布的
而buaa-noob的图像是呈正态分布的
代码:
void solve(){
int n;
cin >> n;
int sum = 0;
for(int i = 0; i < n; i++){
int x, y, r;
cin >> x >> y >> r;
sum += r;
}
sum /= n;
if(sum < 20)
cout << "bit-noob";
else
cout << "buaa-noob";
}
F.鸡数题!
F-鸡数题!_2024牛客寒假算法基础集训营1 (nowcoder.com)
鸡想问有多少个长为 m的数组 a 同时满足以下条件:
1、对于任意的 i, >0 ;
2、对于任意的整数 ,1≤i≤m−1,<
;
3、 |
|...|
|
=
−1 (之中 | 为按位或操作);
4、对于任意的 i≠j ,满足 &
=0 (之中 && 为按位与操作)。
你的答案需要对 取模。
n,m(1≤n,m≤105)
分析:
这是一个第二类斯特林数的问题,拆解为二进制就可以把问题抽象为 n 个不同的小球放到 m 个无区别的盒子里
直接套用第二类斯特林数的结论:
代码:
const int mod = 1e9 + 7;
ll fpow(ll a, ll x){
ll res = a, ans = 1;
while(x){
if(x&1) ans = ans * res % mod;
res = res * res % mod;
x >>= 1;
}
return ans;
}
ll inv(ll a){
return fpow(a, mod-2);
}
ll fz[N], fm[N];
void init(){
fz[0] = fm[0] = 1;
for(ll i = 1; i <= 1000000; i++){
fz[i] = fz[i - 1] * i;
fz[i] %= mod;
}
fm[1000000] = fpow(fz[1000000], mod - 2);
for(ll i = 999999; i >= 1; i--){
fm[i] = fm[i+1] * (i+1);
fm[i] %= mod;
}
}
ll C(ll n, ll k){
if(n < k)
return 0;
return fz[n] * fm[k] % mod * fm[n - k] % mod;
}
void solve(){
int n, m;
cin >> n >> m;
init();
int ans = 0;
for(int i = 0; i <= m; i++){
if(i & 1)
ans = (ans - (C(m, i) * fpow(m - i, n)) % mod) % mod;
else
ans = (ans + (C(m, i) * fpow(m - i, n)) % mod) % mod;
ans = (ans + mod) % mod;
}
ans = ans * fm[m] % mod;
cout << ans << endl;
}