2023牛客寒假算法基础集训营1

2023牛客寒假算法基础集训营1

比赛网址

特别说明:本篇题解代码借鉴了jiangly大佬的AK代码和牛客出题人的思路

官方题目难度排名

image-20230205235542883

再一次说明:本题解只涉及前三难度等级的题目,B题I题J题水平达不到,没有涉及

A.World Final? World Cup! (I)

题目描述

题目跳转

思路点拨

前后缀的思路,代码一看便能理解

提交代码

#include<bits/stdc++.h>

void solve()
{
    std::string s;
    std::cin >> s;
    
    int res[] = {5,5};
    int score[] = {0,0};
    
    
    for(int i=0;i<10;i++)
    {
        int x = i % 2;
        res[x]--;
        score[x] += s[i] - '0';
        if(res[1] + score[1] < score[0] ||res[0] + score[0] < score[1])
        {
            std::cout << i + 1 << "\n";
            return;
        }
    }
 	std::cout << -1 << "\n";
}

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    
    int t;std::cin>>t;
    while(t--)
    {
        solve();
    }
    
    return 0;
}

C.现在是,学术时间 (I)

题目描述

题目跳转

思路点拨

  • 简单贪心
  • 总的H指数会受教授数影响,根据题目定义,首先想到的是让几个人的文章交给一个人去发,但是你要明白一个人就有一篇文章,如果每个人单独发自己的,最少会有引用量大于0的文章的数目大小的H指数,但是如果你多个人的文章交给某一个去发,最多会有这多个人+这一个人的数目大小的H指数,这还必须保证这多个人的每个人的引用量都大于等于这多个人+这一个人的人数数目,所以贪心考虑,直接每个人发自己的文章,只要每个人的引用量大于零,便可以+1。

提交代码

#include<bits/stdc++.h>
using namespace std;


void solve()
{
    int n;cin >> n;
    vector<int> a(n);
    int ans = 0;
    for (int i=0;i<n;i++)
    {
        cin >> a[i];
        if(a[i] > 0) ans ++;
    }
    cout << ans << "\n";
    
    return;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int t;cin >> t;
    while(t--)
    {
        solve();
    }
    
    
    return 0;
}

D.现在是,学术时间 (II)

题目描述

题目跳转

思路点拨

image-20230206120555389

不好证明,但是可以猜结论,猜另外一点肯定是A,B,C,D中其中一点,然后枚举四个点取最大的。

注意利用cout的保留小数代码

std::cout << std::fixed << std::setprecision(10) << "\n";

提交代码

#include<bits/stdc++.h>


void solve()
{
    int x, y, a, b;
    std::cin >> x >> y >> a >> b;
    
    double ans = 0;
    for(auto c:{0,x})
    {
        for(auto d:{0,y})
        {
            int xl = std::min(a, c);
            int yl = std::min(b, d);
            int xr = std::min(x, std::max(a, c));
            int yr = std::min(y, std::max(b, d));
            int inter = (xr-xl)*(yr-yl);
            int uni = x * y + std::abs(a - c) * std::abs(b - d) - inter;
            double res = 1. * inter / uni;
            
            ans = std::max(ans, res);
        }
    }
    
    std::cout << ans << "\n";
}

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    
    std::cout << std::fixed << std::setprecision(10) << "\n";
    
    
    int t;
    std::cin >> t;
    while(t--)
    {
        solve();
    }
    return 0;
}

E.鸡算几何

题目描述

题目跳转

思路点拨

判断第三个操作是否一定用过,利用叉积,第三个操作会改变方向,也就是改变叉积,第一个操作和第二个操作不会改变方向

利用叉积,必须重写结构体(向量减法,必须要同时x和y减),注意这里还需要计算距离,也就是计算点积,判断AB长度和BC长度是否相同。

有一个坑点,ABC和DEF不对应,但是可以利用叉积选择都大于0或者都小于0的任意一边比较,如果用过第三个命令,说明比较的两条直线不相等,对于一种特殊情况也可以包括,如果AB和BC长度相同也可以判断出;当然如果没有用第三个命令,那么本身操作前后两个线就是一条线,长度自然相等。

提交代码

#include<bits/stdc++.h>

using i64 = long long;

using T = double;

struct Point
{
    T x;
    T y;
    Point(T x = 0, T y = 0):x(x),y(y){}
    Point & operator -= (Point &lhs)
    {
        x -= lhs.x, y -= lhs.y;
        return *this;
    }
    
    friend Point operator - (Point lhs,Point &rhs)
    {
        return lhs -= rhs;
    }
};

T dot(const Point &a ,const Point &b)
{
    return a.x * b.x + a.y * b.y;
}

T cross(const Point &a,const Point &b)
{
    return a.x * b.y - a.y * b.x;
}


void solve()
{
    Point a[6];
    for(int i = 0; i < 6; i++)
    {
        std::cin >> a[i].x >> a[i].y;
    }
    
    if(cross(a[0] - a[1], a[2] - a[1]) > 0) std::swap(a[0], a[2]);
    if(cross(a[3] - a[4], a[5] - a[4]) > 0) std::swap(a[3], a[5]);
    
    double len0 = std::sqrt(dot(a[0] - a[1], a[0] - a[1]));
    double len1 = std::sqrt(dot(a[3] - a[4], a[3] - a[4]));
    if(std::abs(len0 - len1) >= 1e-9) puts("YES");
    else puts("NO");
    return ;
}


int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    int t;
    std::cin >> t;
    while(t--)
    {
        solve();
    }
    
    return 0;
}

F.鸡玩炸蛋人

题目描述

题目跳转

思路点拨

  • 联通块,利用并查集DSU

image-20230207222945795

提交代码(DSU板子,借鉴jiangly大佬的)

#include<bits/stdc++.h>
using i64 = long long;

struct DSU
{
    std::vector<int> f,siz;
    DSU(int n) :f(n),siz(n,1){std::iota(f.begin(), f.end(), 0);}
    
    int leader(int x)
    {
        while(x != f[x]) x = f[x] = f[f[x]];
        return x;
    }
    bool same(int a,int b)
    {
        return leader(a) == leader(b);
    }
    bool merge(int a, int b)
    {
        a = leader(a);
        b = leader(b);
        if(a == b) return false;
        siz[a] += siz[b];
        f[b] = a;
        return true;
    }
    int size(int a)
    {
        return siz[leader(a)];
    }
};


int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    
    int n, m;
    std::cin >> n >> m;
    DSU dsu(n);
    
    for(int i = 0; i < m ; i++)
    {
        int u,v;
        std::cin >> u >> v;
        u--,v--;
        dsu.merge(u,v);
    }
    
    int comp = 0;
    std::vector<int> s(n,0);
    for(int i = 0; i < n;i++)
    {
        int c;
        std::cin >> c;
        if(c)
        {
            int x = dsu.leader(i);
            comp += !s[x];
            s[x] += c;
        }
    }
    
    
    i64 ans = 0;
    for(int i = 0; i < n; i++)
    {
        if(dsu.leader(i) == i)
        {
            if(comp - (s[i] > 0) == 0)
            {
                int s = dsu.size(i);
                ans += 1ll * s * s;
            }
        }
    }
    
    std::cout << ans << "\n";
    return 0;
}

G.鸡格线

题目描述

题目跳转

思路点拨

  • 维护到叶子结点的线段树,不需要懒标记
  • 对于\(f(x) = round(10\sqrt x )\)来说,0,100,99得到的结果是循环的,所以这三个情况需要特殊处理,注意这里的线段树需要存储一个区间段的最大最小值,只为判断一个区间的范围在99~100之间就可以不用操作,减少时间浪费。

提交代码

#include<bits/stdc++.h>
using i64 = long long;

constexpr int N = 1 << 18;

i64 sum[N];
int max[N], min[N];

void pull(int o)
{
    max[o] = std::max(max[2 * o], max[2 * o + 1]);
    min[o] = std::min(min[2 * o], min[2 * o + 1]);
    sum[o] = sum[2 * o] + sum[2 * o + 1];
}

void build_tree(int o, int l, int r, auto &a)
{
    if(r - l == 1)
    {
        max[o] = min[o] = sum[o] = a[l];
        if(!a[l]) max[o] = 100, min[o] = 100;
        return;
    }
    
    int mid = ( l + r ) >> 1;
    build_tree(2 * o, l, mid, a);
    build_tree(2 * o + 1, mid, r, a);
    pull(o);
}

void modify(int o, int l, int r, int x, int y, int k)
{
    if(max[o] <= 100 && min[o] >= 99) return;
    if(l >= y|| x >= r) return ;
    if(r - l == 1)
    {
        while(k && max[o] != 100 && max[o] != 99)
        {
            k--;
            max[o] = std::sqrt(max[o]) * 10 + .5;
        }
        
        min[o] = sum[o] = max[o];
        return ;
    }
    int mid = (l + r) >> 1;
    modify(2 * o, l, mid, x, y, k);
    modify(2 * o + 1, mid, r, x, y, k);
    pull(o);
}


int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    
    
    int n, m;
    std::cin >> n >> m;
    std::vector<int> a(n);
    for(int i = 0; i < n; i++)
    {
        std::cin >> a[i];
    }
    
    
    build_tree(1,0,n,a);
    while(m--)
    {
		int op;
        std::cin >> op;
        
        if(op == 1)
        {
            int l, r, k;
            std::cin >> l >> r >> k;
            l--;
            modify(1,0,n,l,r,k);
        }
        else
        {
            std::cout << sum[1] << "\n";
        }
    }
    return 0;
}

H.本题主要考察了DFS

题目描述

题目跳转

思路点拨

诈骗,不用求出对应那个是题目中的“1”标签,还是“2”标签,只问你最后一块的成本,用整个大的减去剩余块的成本就是答案

提交代码

#include<bits/stdc++.h>


void solve()
{
    int n;
    std::cin >> n;
    int sum = 0;
    for(int i = 0; i < n * n - 1; i++)
    {
        std::string s;
    	std::cin >> s;
        sum += 10;
        for(int i = 0; i < 4;i++)
        {
           if(s[i] == '1') sum--;
           if(s[i] == '2') sum++;
        }
    }
    int total = 10 * n * n;
    std::cout << total - sum << "\n";
    return ;
}

int main()
{
    int t;
    std::cin >> t;
    while(t--)
    {
        solve();
    }
    
    return 0;
}

K.本题主要考察了dp

题目描述

题目跳转

思路点拨

  • 法一:利用动态规划(推荐,训练思维)

  • \(dp[j][x][y]\)表示状态,j是指当前枚举到第几个1了,x和y分别指当今序列后两位代表的数字,注意原本应该为四维,第一维度为当前选择的第几个,但是可以省去这一维,但是必须存储上一层的状态,利用辅助三维数组\(g[j][x][y]\)

  • $g[j+z][y][z] =min(g[j+z][y][z],dp[j][x][y] + 0) $ \(i<2时,\)

  • $g[j+z][y][z] =min(g[j+z][y][z],dp[j][x][y] + (x+y+z>=2) $ \(i>=2时\)

  • 最后答案是\(dp[m][0][0],dp[m][0][1],dp[m][1][0],dp[m][1][1]\)中最小的一个。

  • i>=2才可转移,原因是最少需要有三位,根据题意,这里i从0开始计数。

  • 利用的c++语法,构造三维数组,并且所有的数赋值为\(\infty\),即INF:

  • 单独赋值用法
    int m = 19;
    std::vector dp(m+1, std::array<std::array<int,3>,3>{1,2,3,4,5,6,7,8,9});
    std::cout << dp[11][0][0] <<" ";
    std::cout << dp[18][0][1] <<" ";
    std::cout << dp[19][0][2] <<" ";
    std::cout << dp[10][1][0] <<" ";
    std::cout << dp[13][1][1] <<" ";
    std::cout << dp[15][1][2] <<" ";
    std::cout << dp[12][2][0] <<" ";
    std::cout << dp[17][2][1] <<" ";
    std::cout << dp[14][2][2] <<"\n";
    
  • 法二:贪心构造

  • 考虑1001001001001……肯定是构造的最少的具有坏区间的序列,多余的1放到后面

  • 存在如下情况

    • 对于低于2位的串,n%3 == m,或者n<=2
      • 答案为0
    • 以100100……结尾或者000……结尾或者以1001结尾的,n/3 >= m-1
      • 答案为0
    • 以1001111……结尾,注意这里不包括1001结尾的,
      • 答案为\(m-\dfrac{n-m}{2}-2+1\)
      • 注意011算是1个坏区间,注意这里不包括1001结尾的,不能构成一个坏区间,不满足如上公式,
    • 以1001011111……结尾
      • 答案为\(m-\dfrac{n-m}{2}-1-2+2\)
    • 直接都是全1串,n==m ,
      • 答案为n-2
  • 统一来说

  • n==m时,ans = n-2;

  • n%3==m || n/3>=m-1 ,ans = 0;

  • 其他,ans = \(m-\dfrac{n-m}{2}-1\)

提交代码

  • 法一:动态规划
#include<bits/stdc++.h>

constexpr int INF = 1e9;

void solve()
{
    int n,m;
    std::cin >> n >> m;
    
    std::vector dp(m+1,std::array<std::array<int,2>,2 >{INF,INF,INF,INF});
    dp[0][0][0] = 0;
     for(int i = 0; i < n; i++)
    {
       	std::vector g(m+1,std::array<std::array<int,2>,2 >{INF,INF,INF,INF});
        for(int j = 0; j <= m; j++)
        {
            for(auto x:{0,1})
            {
                for(auto y:{0,1})
                {
                    for(auto z:{0,1})
                    {
                        if(j+z > m) continue;
                        int &res = g[j+z][y][z];
                        res = std::min(res, dp[j][x][y] + (i>=2 && x + y + z >= 2));
                    }
                }
            }
        }
        std::swap(dp,g);
    }
    int ans = INF;
    for(auto x:{0,1})
    {
        for(auto y:{0,1})
        {
            ans = std::min(ans,dp[m][x][y]);
        }
    }
    std::cout << ans << "\n";
    
}

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    
    int t = 1;
    while(t--)
    {
        solve();
    }
    return 0;
}
  • 法二:贪心构造
#include<bits/stdc++.h>

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    
    int n, m;
    std::cin >> n >> m;  
    if(n==m) std::cout << n-2 << "\n";
    else if(n <= 2 || n/3 >= m - 1) std::cout << 0 << "\n";
    else std::cout<<m-(n-m)/2-1 << "\n";
    return 0;
}

L.本题主要考察了运气

题目描述

题目跳转

思路点拨

  • 方法1就是wa31发
  • 方法2可以通过期望计算得到

image-20230206001712151

  • 注意计算的是次数的期望
    公式为:x*p相加即可
  • 具体计算公式如下:

\[1(次) \times 0.2+2(次) \times 0.2+3(次) \times 0.2 +4(次) \times 0.4+1(次) \times 0.25+2(次) \times 0.25+3(次) \times 0.5 = 5.05(次) \]

  • \(5.05-3.55 = 1.6\)\(\dfrac{1.6}{5}=32\)

提交代码

#include<bits/stdc++.h>
using namespace std;


int main()
{
    cout<<32<<"\n";
    return 0;
}

M.本题主要考察了找规律

题目描述

题目跳转

思路点拨

转移方程:

\(dp[j - k] = max(dp[j - k] ,dp[j]+1.*k/j)\); 第一维就是当前多少个仙贝,直到所有仙贝都用完了

答案为\(dp[0]\)

提交代码

#include<bits/stdc++.h>


int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    
    int n,m;
    std::cin >> n >>m;
    std::vector<int> dp(n+1);
    
    for(int i = 0; i < n; i++)
    {
        for(int j = 0; j < m; j++)
        {
            for(int k = 1; k <= j; k++)
            {
                dp[j - k] = max(dp[j - k],dp[j] + 1. * k / j); 
            }
        }
    }
    
    std::cout << fixed << setprecision(10) << dp[0] << "\n";
    return 0;
    
}
posted @ 2023-02-07 23:48  哲远甄骏  阅读(38)  评论(0)    收藏  举报