2025牛客寒假算法基础集训营2

题目链接:2025牛客寒假算法基础集训营2

总结:排名:113 10题 803罚时

  • 分层图最短路

A. 一起奏响历史之音!

tag:语法

void solve(){
    set<int> st;
    st.insert(1);
    st.insert(2);
    st.insert(3);
    st.insert(5);
    st.insert(6);
    for (int i = 0; i < 7; i ++){
        int x;
        cin >> x;
        if (st.find(x) == st.end()){
            cout << "NO\n";
            return;
        }
    }

    cout << "YES\n";
}

B. 能去你家蹭口饭吃吗

tag:中位数

void solve(){
   int n;
   cin >> n;
   vector<int> a(n);
   for (int i = 0; i < n; i ++){
        cin >> a[i];
   } 
   sort(a.begin(), a.end());

    cout << a[n / 2] - 1 << endl;

}

C. 字符串外串

tag:构造

Description:给定一个n和m,构造一个长度为n,可爱度为m的字符串。如果不能输出-1。

Solution:从左往右构造先构造长度为m的串,然后让第m个字母在末尾出现,那么剩余n - m个字母不能出现两次,因此n - m <= 26。

  • 但是为了避免从右往左看可爱度过大,我们需要周期性的构造。
void solve(){
    int n, m;
    cin >> n >> m;
    if (n == m || n > m + 26) {
	    cout << "NO\n";
    }
    
    cout << "YES\n";
    int now = -1;
    for (int i = 0; i < m; i++){
        now = (now + 1) % 26;
        char c = 'a' + now;
        cout << c;
    }
    char last = 'a' + now;
    for (int i = m; i + 1 < n; i++){
        now = (now + 1) % 26;
        char c = 'a' + now;
        cout << c;
    }
    cout << last;
    cout << "\n";
}

D. 字符串里串

tag:思维

Description:定义可爱度为一个最大的整数k,使得存在长度为a的连续子串、长度为b的不连续子序列,满足a == b。给定一个字符串,求该字符串的可爱度。不连续子序列至少由两段不相邻的非空子串构成。

Solution:仔细观察一下,在确定a之后,b最好的选择是在a第一个字符前面找一个和a[1]一样的字符或者在a的最后一个字符后面找一个和a[n]一样的字符。

  • 那么就变成了找每一个字符出现两次时,串的最大长度。
void solve(){
    int n;
    cin >> n;
    string s;
    cin >> s;
    s = '$' + s;

    set<int> st;
    int ans = 0;
    for (int i = 1; i <= n; i ++){  // 从前往后
        if (st.find(s[i]) != st.end()){
            ans = max(ans, n - i + 1);
        }
        st.insert(s[i]);
    }

    st.clear();
    for (int i = n; i >= 1; i --){ // 从后往前
        if (st.find(s[i]) != st.end()){
            ans = max(ans, i);
        }
        st.insert(s[i]);
    }
    if (ans == 1)  // b的长度至少为2
        ans = 0;
    cout << ans << endl;

}

E. 一起走很长的路!

tag:前缀和,st表

Description:给定一个长度为n的数组,给定k次询问,每次询问给定l,r需要给出操作次数,每次操作可以在一个位置进行+1或者-1操作。

  • 每次操作时:推倒a[l],如果a[l] >= a[l + 1],则推倒a[l + 1],如果a[l] + a[l + 1] >= a[l + 2],则推倒a[l + 2]...,需要给出最少操作多少次能推倒给定区间的所有牌。

Solution:显然+1操作比-1操作好,显然将+1操作放在最前面好。

  • 一个数想要被推到需要前缀和大于该数。考虑记录每个数减去其前缀和,那么需要的操作等于区间最大值再加上一段前缀和(该段前缀和并不影响区间最大值)。
int st[N][21];
int n, k;

void init() {
    for (int j = 1; j <= 20; j ++){
        for (int i = 1; i + (1 << j) - 1 <= n; i ++) {
            st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
        }
    }
}

int query(int l, int r){
    int k = log2(r - l + 1);
    return max(st[l][k], st[r - (1 << k) + 1][k]);
}

void solve(){
    cin >> n >> k;
    
    vector<int> a(n + 1), s(n + 1);

    for (int i = 1; i <= n; i ++) {
        cin >> a[i];
        s[i] = s[i - 1] + a[i];
        st[i][0] = a[i] - s[i - 1];
    }

    init();
    while (k --){
        int l, r;
        cin >> l >> r;
        if (l == r) {
            cout << 0 << endl;
            continue;
        }
        else{ // 最大值询问从l + 1开始,因为a[l]一开始就被推倒
            cout << max(0ll, s[l - 1] + query(l + 1, r)) << endl;
        }
    }
}

F. 一起找神秘的数!

tag:打表

Solution:打表看x + y = (x or y) + (x and y)+ (x xor y)的数的性质,发现当x == y时,才成立。

void solve(){
    int l, r;
    cin >> l >> r;
    cout << r - l + 1 << endl;
    
    for (int i = 0; i < 1000; i ++)
        for (int j = i; j < 1000; j ++){
            if (i + j == (i | j) + (i & j) + (i ^ j)){
                debug(i, j);
            }
        }

}

G. 一起铸最好的剑!

tag:模拟

void solve(){
    int n, m;
    cin >> n >> m;
    int t = m;
    
    int ans = 1;
    int mi = abs(n - m);

    while (1){
        if (mi > abs(n - m * t)){
            ans ++;
            m *= t;
            mi = abs(n - m);
        }
        else
            break;
    }
    cout << ans << endl;
}

H. 一起画很大的圆!

tag:数学

Description:给定一个矩形区域,在其中找出三个整数点,使得过这三个点画出的圆的半斤最大。

Solution:三个不共线的点可以确定一个圆,如果三点越接近一条直线,这个圆最大。

  • 猜一下一个点在边界,那么另外两个点就很好猜了。在长边上选一个与边界最近的点,在另一个条边上选一个离长边最近的点。
void solve(){
    int a, b, c, d;
    cin >> a >> b >> c >> d;

    if (b - a > d - c){
        cout << a << " " << d << endl;
        cout << a + 1 << " " << d << endl;
        cout << b << " " << d - 1 << endl;
    }
    else{
        cout << a << " " << d << endl;
        cout << a << " " << d - 1 << endl;
        cout << a + 1 << " " << c << endl;
    }

}

I. 一起看很美的日落!

tag:树形DP

Description:给定一颗n个节点的树,每个节点的权值为\(a_i\),定义一个连通块的权值为两两节点的权值异或和,求全部连通块的异或和。

Solution:求异或和可以转化为按位求每一个01的个数

  • dp[u]:包含u这个节点的子树的所有答案。
  • g[u]:包含u这个节点的子树的连通块个数。
  • f[u][0/1]:保护u这个节点的0的贡献次数和1的贡献次数。
  • g[u] = g[u] + g[u] * g[v]
  • f[u][0/1] = f[u][0/1] + f[u][0/1] * g[v] + f[v][0/1] * g[u]
  • dp[u] = dp[u] + dp[u] * g[v] + dp[v] * g[u] + f[u][0/1] * f[v][1/0] + f[v][0/1] * f[u][1/0]
const int N = 1e5 + 10;

int n;
int dp[N], g[N];
int f[N][31][2];

void solve(){
    cin >> n;
    vector e(n + 1, vector<int>());
    vector<int> a(n + 1);

    for (int i = 0; i < n; i ++) {
        cin >> a[i + 1];
    }

    for (int i = 0; i < n - 1; i ++) {
        int u, v;
        cin >> u >> v;
        e[u].pb(v), e[v].pb(u);
    }

    int ans = 0;
    function<void(int, int)> dfs = [&](int u, int fa) {
        for (int i = 0; i < 31; i ++) {
            if ((a[u] >> i) & 1) {
                f[u][i][1] = 1;
            }
            else
                f[u][i][0] = 1;
        }
        g[u] = 1;

        for (auto v : e[u]) {
            if (v == fa)
                continue;
            dfs(v, u);  // 先计算好儿子的答案
            dp[u] = (dp[u] + dp[u] * g[v] % mod + dp[v] * g[u] % mod) % mod;
            for (int i = 0; i < 30; i ++) {
                dp[u] = (dp[u] + (1ll << i) * f[u][i][0] % mod * f[v][i][1] % mod + (1ll << i) * f[v][i][0] % mod * f[u][i][1] % mod) % mod;
                f[u][i][0] = (f[u][i][0] + f[u][i][0] * g[v] % mod + f[v][i][0] * g[u] % mod) % mod;
                f[u][i][1] = (f[u][i][1] + f[u][i][1] * g[v] % mod + f[v][i][1] * g[u] % mod) % mod;
                if (i == 0 || i == 1) {
                    debug(i, u, f[u][i][0], f[u][i][1]);
                }
            }
            g[u] = (g[u] + g[u] * g[v]) % mod;
        }
        ans = (ans + dp[u]) % mod;
    };

    dfs(1, 0);
    cout << (ans * 2) % mod << endl;
}

J. 数据时间?

tag:模拟

Solution:注意观察细节,可以减少很多码量。

void solve(){
    int n;
    string h, m;
    cin >> n >> h >> m;
    if (m.size() == 1){
        m = '0' + m;
    }
    string x, y, z;
    map<string, int> cnt1, cnt2, cnt3;
    for (int i = 0; i < n; i++){
        cin >> x >> y >> z;
        if (y.substr(0, 4) != h) continue;
        if (y.substr(5, 2) != m) continue;
        string z2 = z.substr(0, 2);
        if (z2 == "07" || z2 == "08" || z == "09:00:00" || z2 == "18" || z2 == "19" || z == "20:00:00"){//cnt1
            cnt1[x]++;
        }else if (z2 == "11" || z2 == "12" || z == "13:00:00"){
            cnt2[x]++;
        }else if (z2 == "22" || z2 == "23" || z2 == "00" || z == "01:00:00"){
            cnt3[x]++;
        }
    }
    cout << cnt1.size() << " " << cnt2.size() << " " << cnt3.size() << "\n";
}

K. 可以分开吗?

tag:dfs

Solution:dfs找出所有连通块,同时将连通块周围的灰块放入set去重。

Competing:将字符串当做整数读了(o(╥﹏╥)o)。

int dir[4][2] = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};

void solve(){
    int n, m;
    cin >> n >> m;

    vector a(n + 1, vector<pii>(m + 1));
    for (int i = 1; i <= n; i ++){
        string s;
        cin >> s;
        s = '$' + s;
        for (int j = 1; j <= m; j ++){
            a[i][j].fi = s[j] - '0';
        }
    }
    
    int ans = n * m;
    set<pii> st;

    function<void(int, int)> dfs = [&](int x, int y){
        for (int i = 0; i < 4; i ++){
            int tx = x + dir[i][0], ty = y + dir[i][1];
            if (tx < 1 || tx > n || ty < 1 || ty > m || a[tx][ty].se != 0)
                continue;
            if (a[tx][ty].fi == 0){  
                st.insert({tx, ty});
                continue;
            }
            a[tx][ty].se = 1;
            dfs(tx, ty);
        }
    };

    for (int i = 1; i <= n; i ++){
        for (int j = 1; j <= m; j ++){
            if (a[i][j].fi == 1 && a[i][j].se == 0){
                st.clear();
                dfs(i, j);
                ans = min(ans, (int)st.size());
            }
        }
    }

    cout << ans << endl;
}

M. 那是我们的影子

tag:计数 + 组合数学

Description:给定一个3 * n的表格,里面有一些数字和一些问号,你可以在问号处填任意数字(0-9),问满足任意3 * 3的表格都是数独的方案数?。

Solution:模拟一下发现j mod 3相同的列里面填的数字一定相同(顺序可以不一样)。

  • 一个列集出现的数字大于3个不合法。
  • 一个数字出现在多个列集不合法。
  • 一列出现相同的数字不合法。
  • 对于合法的状态:
    • 当前还剩n个数字可用,第一列集还可以填ne[0] = 3 - st[0].size()个数,......。
    • 那么方案数为\(C_{n}^{ne[0]} * C_{n - ne[0]}^{ne[1]} * C_{n - ne[0] - ne[1]} ^ {ne[2]}\)
    • 一列有x个问号,代表着x个数的顺序可以任意交换,即\(A_{x}^{x}\)
struct Comb{
    int n, mod;
    Comb(int n, int mod){
        this->n = n;
        this->mod = mod;
        init(n);
    };

    LL qmi(LL a, LL k, LL mod){
        LL res = 1;
        while (k){
            if (k & 1) 
                res = res * a % mod;
            k >>= 1;
            a = a * a % mod;
        }
        return res;
    }
    vector<LL> fact, infact, inv;
    void init(int n){
        fact.resize(n + 1);  // 存储阶乘
        infact.resize(n + 1);  // 存储阶乘的逆元
        inv.resize(n + 1);  // 存储自然数的逆元

        fact[0] = infact[0] = inv[1] = 1;
        for (int i = 1; i <= n; i ++)
            fact[i] = fact[i - 1] * i % mod;
        infact[n] = qmi(fact[n], mod - 2, mod);
        for (int i = n - 1; ~i; i --)
            infact[i] = infact[i + 1] * (i + 1) % mod;
        for (int i = 2; i <= n; i ++)
            inv[i] = (mod - mod / i) * inv[mod % i] % mod;
    }

    LL C(LL n, LL m){
        if (m < 0 || n - m < 0)
            return 0;
        return fact[n] * infact[m] % mod * infact[n - m] % mod;
    }

    LL A(LL n, LL m){
        if (n < m || m < 0)
            return 0;
        return fact[n] * infact[n - m] % mod;
    }
};

void solve(){
    int n;
    cin >> n;
    Comb comb(20, mod);
    
    vector g(4, vector<char>(n + 1));
    set<int> st[3];

    for (int i = 1; i <= 3; i ++)
        for (int j = 1; j <= n; j ++){
            cin >> g[i][j];
            if (g[i][j] == '?')
                continue;
            st[j % 3].insert(g[i][j] - '0');
        }

    if (st[0].size() > 3 || st[1].size() > 3 || st[2].size() > 3){  // 某一列集出现3个以上的数
        cout << 0 << endl;
        return;
    }
    
    for (int i = 1; i <= 9; i ++){
        bool flag = false;
        for (int j = 0; j < 3; j ++){
            if (st[j].find(i) != st[j].end() && flag){  // 不同列集包含同一个数
                cout << 0 << endl;
                return;
            }
            if (st[j].find(i) != st[j].end()){
                flag = true;
            }
        }
    }
    
    for (int i = 1; i <= n; i ++) {
        vector<int> cnt(10);
        for (int j = 1; j <= 3; j ++) {  // 同一个数在同一列出现两次
            if (g[j][i] == '?')
                continue;
            cnt[g[j][i] - '0'] ++;
            if (cnt[g[j][i] - '0'] > 1){
                cout << 0 << endl;
                return;
            }
        }
    }

    vector<int> f(n + 1);
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= 3; j ++) {  // 每一列有多少个问号
            if (g[j][i] == '?') {
                f[i] ++;
            }
        }

    vector<int> ne(3);  // 每一列还剩多少数可以用
    int now_use = 0;  // 一共还有多少数可以用
    for (int j = 0; j < 3; j ++) {
        ne[j] = 3 - st[j].size();
        now_use += ne[j];
    }

    // 每一列能够填的数字是哪些
    int ans = comb.C(now_use, ne[0]) * comb.C(now_use - ne[0], ne[1]) % mod;

    for (int i = 1; i <= n; i ++)  // 一列选择的数确定了,那么有几个问号代表可以交换它们的位置
        ans = ans * comb.A(f[i], f[i]) % mod;
    
    cout << ans << endl;
}
posted @ 2025-02-09 12:57  Sakura17  阅读(15)  评论(0)    收藏  举报