2024牛客寒假算法基础训练营1

牛客竞赛_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ

注:本题解只涉及赛时思路和赛后补题收获,人菜佬勿喷

赛时六题蒟蒻【ABCGLM】 补题:【EFI】

A.DFS搜索

A-DFS搜索_2024牛客寒假算法基础集训营1 (nowcoder.com)

给定一个长度为n的字符串s,求子序列中是否含有 “DFS",“dfs" 子串

1≤n≤50

分析:

n的范围很小,赛时第一个想法直接暴力枚举,时间复杂度 O(n^3T) 

代码:

(史山

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 ,长为 2*10^9+1 的管道中,有一只鸡和若干个着火点。鸡可以在管道内上下左右移动。

鸡初始在 (1,0) 处,现在给定 n 个着火点的坐标,求出为了不让鸡逃出管道,最少要添加多少个着火点。

0\leq n\leqslant 10^9

求出需要添加多少个着火点才不能让鸡逃出去。

分析:

以原点(鸡最初的位置)为中点,将管道分为两段(负数为左侧,正数右侧。文章下以左右侧称)

对于同侧的每两个着火点,只要它们能组成一条竖线或者左右两条对角线(下标相邻)即可将此侧封住

同时考虑特殊情况将(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 个人办事所需时间为 t_i

时刻 0 所有人都进入办事大厅,第 i 个人的不满意度 D_i 定义为他的事情办完的那个时刻。定义所有人的总不满意度S=\sum_{i=1}^{n}D_i

办事处工作人员会合理安排办事顺序,使得总不满意度最小,记为 S_{min} ​。

现在,很急的鸡来办事了,鸡可以在任意时刻要求工作人员放下手头的事情,立刻来处理鸡的事情,鸡的事情需要t_c时间处理完成。假设鸡插队后其余 n 人的总不满意度最小值变为 S_c ,若 S_c-S_{min}\leq M ,则工作人员将允许鸡的插队,否则工作人员将拒绝。 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 个优惠可以表示为"满a_i 元减 b_i元",多个满减优惠可以叠加。

满减的具体结算流程是:假设鸡购买的食物原价共为 x 元,则所有满足 x\geq a_i 的满减优惠都可以一起同时被使用,优惠后价格记为 y ,则鸡只要支付 y 元就可以了(若 y≤0 则不需要支付)。

现在,鸡的手机里一共只有m 元钱,鸡想知道,他所购买的食物原价 x 最多为多少。

分析:

这道题的关键是所有满足 x\geq a_i 的满减优惠可以叠加。

只需按照 a_i 的大小从小到大将 a_i 和 b_i进行结构体排序,然后遍历一遍如果当前的 b_i 加上累加的优惠 ≥ 当前的 a_i ,那就更新最大值。

同时注意数据范围,需要开 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 ,可以用如下公式表示:

\left\{\begin{matrix} &x=-c \\ & z\geq 0 \end{matrix}\right.

2、一面宽为 2w 高为 ℎ 的绿墙 W (厚度忽略不计),可以用如下公式表示:

\left\{\begin{matrix} &x=0 & \\ &-w\leq y\leq w & \\ &0\leq z\leq h & \end{matrix}\right.

3、这个世界唯一的点光源 L(体积忽略不计),点光源必须在图中的黑色线段上,黑色线段可以用如下公式表示:

\left\{\begin{matrix} &x=c & \\ &y=0 & \\ &0\leq z\leq d & \end{matrix}\right.

神的目的是照亮这片大地,但同时也要防止人类给点阳光就灿烂。因此,神可以决定放置点光源L的位置(神必须放置点光源),使得未被照亮的土地面积尽可能大,请你输出未被照亮的土地面积的最大值

说明:对于地面上的一点,若其与点光源L的连线接触到了绿墙W,则该点未被照亮。

注意:神只想最大化未被照亮的土地面积最大值,墙面S上未被照亮的面积不算做土地面积。且我们不计算墙S背后(x轴负方向)的未被照亮的土地面积。

分析:

很明显的一道数学题(高中数学?

可以取临界值 2ℎ ,正好可以打到墙的最底端,根据相似三角形可以算出阴影梯形面积即为 3*w*c

代码:

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 只鸡截至目前已经积了 a_i分。

接下来还有 m 场比赛要进行,第 i 场比赛的对阵双方是编号为 u_i 和 v_i 的鸡。积分规则是:胜方加三分,败方不得分,若战平则双方各得一分。

请你计算在最好的情况下,我们的一号选手(炸鸡)能够排到第几名。

注意若有多鸡并列,则排名取并列的排名,且不影响随后的排名(例如两只鸡并列第二名,则都视为第二名,排名其后的下一只鸡视为第四名)。

T(1\leqslant T\leq 100)

n,m(2\leq n\leq 10,1\leq m\leq 10)

a_i(0\leq a_i\leq 100)

u_i,v_i(1\leq u_i,v_i\leqslant n,u_i\neq v_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为学生们布置了一项作业:

"随机生成 10^5 个平面上的圆,使得这些圆满足:圆心坐标为整点、圆半径为整数。所有圆上的每一个点都在 和−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, a_i>0 ;

2、对于任意的整数 ,1≤i≤m−1,a_i<a_{i+1};

3、 a_1|a_2|...|a_{m-1}|a_m=2^n−1 (之中 | 为按位或操作);

4、对于任意的 i≠j ,满足 a_i&a_j=0 (之中 && 为按位与操作)。

你的答案需要对10^9+7 取模。

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;
}
  • 23
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值