第十六届蓝桥杯省赛C/C++B组赛后总结与复盘(ing)

叠Buff

大一小白第一次参加篮球杯,本人太菜了,大概只有五十分左右,只能做出部分简单题

A.移动距离

题目链接:P12130 [蓝桥杯 2025 省 B] 移动距离 - 洛谷

纯数学题,刚看到的时候没有什么思路,把后面简单一点的做了才回来看这题,感觉应该要先一直往右走,在沿着圆弧走,还好猜对了,应该可以有严谨的数学证明的,不过我太菜了感觉好复杂.。最后答案应该是1576

#include <bits/stdc++.h>
#define endl '\n'
using namespace std;

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cout << int(sqrt(666 * 666 + 233 * 233) * (1 + atan(666.0 / 233))) << endl;
    return 0;
}

B.客流量上限

 题目链接:P12131 [蓝桥杯 2025 省 B] 客流量上限 - 洛谷

我太菜了,还不会,答案是2^{1012}mod\ 1000000007

C.可分解的正整数

 题目链接:P12132 [蓝桥杯 2025 省 B] 可分解的正整数 - 洛谷

开始脑子瓦特了,想着一个数是合数就可以,然后发现是质数好像也可以,最后敲了一坨,发现合数和质数。。。不就是大于1的正整数吗。本题判断每个数是否为1即可,因为大于1的任意数都可以表示成 ..., 0, 1, ..., 而1只能表示成0,1,是不合法的。

#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
int n, ans = 0;

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n;
    for (int i = 0; i < n; i ++ )
    {
        int x;
        cin >> x;
        if (x != 1) ans ++ ;
    }
    cout << ans << endl;
    return 0;
}

D.产值调整

 题目链接:P12133 [蓝桥杯 2025 省 B] 产值调整 - 洛谷

观察题目数据范围后发现都是1e9级别的,太大了,简单模拟几个样例后发现三个数在不断地趋近,操作次数到某一定量时三个数会变得相同,接下来继续操作是没有意义的,并且操作次数不会太多,所以不断模拟操作即可,在三者相同或k为0时退出循环

#include <bits/stdc++.h>
#define endl '\n'
using namespace std;

void solve()
{
    int a, b, c, k;
    cin >> a >> b >> c >> k;
    while ((a != b || a != c || b != c) && k)
    {
        k -- ;
        int x = a, y = b, z = c;
        a = (y + z) / 2;
        b = (x + z) / 2;
        c = (x + y) / 2;
    }
    cout << a << " " << b << " " << c << endl;
}

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t -- ) solve();
    return 0;
}

E.画展布置

 题目链接:P12134 [蓝桥杯 2025 省 B] 画展布置 - 洛谷

容易发现我们需要先将数组a进行排序,因为在选择三个及三个以上画作时,假设三个画作xyz的艺术价值大小为x < y > z, 协调性为y - x + y - z, 而把xyz排序为x < z < y后,协调性变为z - x + y - z,与前者相比差异在于y - x 和 z - x,而y > z, 故y - x > z - x, 所以排序后的协调性是小于乱序的协调性的。所以将数组a排序后比较每个连续区间长度为m的协调性,取消最小值即可,因为a_2 - a_1 + a_3 - a_2 + ... + a_m - a_{m - 1} = a_m - a_1, 所以求一段区间[l, r]的协调性可简化为a_r - a_l

#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long LL;
const int N = 100010;
int n, m;
LL res = 1e18;
LL a[N];

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) cin >> a[i], a[i] *= a[i];
    sort(a + 1, a + n + 1);
    for (int l = 1, r = m; r <= n; l ++ , r ++ ) res = min(res, a[r] - a[l]);
    cout << res << endl;
    return 0;
}

F.水质检测

 题目链接:P12135 [蓝桥杯 2025 省 B] 水质检测 - 洛谷

我太菜了,赛时没做对,贪心部分有问题,导致我的结果比正确答案可能会小一点,不过应该也能骗点分,这题做法应该有很多,参考苯环大佬的题解(题解:P12135 [蓝桥杯 2025 省 B] 水质检测 - 洛谷专栏,膜拜大佬),我差不多理解了,将每一列分成三类,从左往右依次考虑。若当前列为第一类,上一个状态为第二类,在第一行和第二行铺设所需要的个数是相同的,但是此时在第二行铺设一定更优,因为可以将当前状态变为第三类。其他情况较易理解

#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
int ans, last = -1, state;
// state: 1  2  3
//        #  .  #
//        .  #  #
string a, b;

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> a >> b;
    for (int i = 0; i < a.size(); i ++ )
    {
        if (a[i] == '.' && b[i] == '.') continue;
        if (last != -1) ans += i - last - 1;
        if (a[i] == '#' && b[i] == '.')
        {
            if (state == 2)
            {
                ans ++ ;
                state = 3;
            }
            else state = 1;
        }
        else if (a[i] == '.' && b[i] == '#')
        {
            if (state == 1)
            {
                ans ++ ;
                state = 3;
            }
            else state = 2;
        }
        else state = 3;
        last = i;
    }
    cout << ans << endl;
    return 0;
}

G.生产车间

 题目链接:P12136 [蓝桥杯 2025 省 B] 生产车间 - 洛谷

我太菜了,直接输出的w[1],赛时没做出来,没想清楚如何枚举每个节点可能的情况,原来可以直接开个set,合法的直接insert就行了,开个vector<bool>应该也是一样的,可能时间效率会好一点,不过注意set会自动去重并排序,所以在枚举每种情况时,当x+y>w[u]时直接break而不是contiunue,当前值都已经比较大了后面的只会更大。g[u].szie()==1&&u!=1判断该节点是否为叶子节点,可能的情况只有两种即选或不选{0, w[u]}。该代码可以勉强大部分测试点,但是感觉没什么测试点可以卡的太死的,时间复杂度最坏情况是 O(nw^2) 的,可以考虑用bitset优化不过本人太菜了不太会

#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
const int N = 1010;
int n;
int w[N];
vector<int> g[N];

set<int> dfs(int u, int p)
{
    if (g[u].size() == 1 && u != 1) return {0, w[u]};
    set<int> s{0};
    for (auto v : g[u])
    {
        if (v == p) continue;
        auto t = dfs(v, u);
        auto st = s;
        for (auto x : st)
            for (auto y : t)
            {
                if (x + y > w[u]) break;
                s.insert(x + y);
            }
    }
    return s;
}

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i ++ ) cin >> w[i];
    for (int i = 1; i < n; i ++ )
    {
        int a, b;
        cin >> a >> b;
        g[a].push_back(b);
        g[b].push_back(a);
    }
    auto t = dfs(1, 0);
    cout << *t.rbegin() << endl;
    return 0;
}

bitset优化版(感觉大差不差)

// G.生产车间
// bitset
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
const int N = 1010;
int n;
int w[N];
vector<int> g[N];

bitset<N> dfs(int u, int p)
{
    bitset<N> dp;
    dp[0] = 1;
    if (g[u].size() == 1 && u != 1)
    {
        dp[w[u]] = 1;
        return dp;
    }
    for (auto v : g[u])
    {
        if (v == p) continue;
        auto t = dfs(v, u);
        auto s = dp;
        for (int i = 0; i <= w[u]; i ++ )
            for (int j = 0; j <= w[v]; j ++ )
            {
                if (i + j > w[u]) break;
                if (s[i] && t[j]) dp[i + j] = 1;
            }
    }
    return dp;
}

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i ++ ) cin >> w[i];
    for (int i = 1; i < n; i ++ )
    {
        int a, b;
        cin >> a >> b;
        g[a].push_back(b);
        g[b].push_back(a);
    }
    auto ans = dfs(1, 0);
    for (int i = w[1]; i >= 0; i -- )
        if (ans[i])
        {
            cout << i << endl;
            break;
        }
    return 0;
}

H.装修报价

 题目链接:P12137 [蓝桥杯 2025 省 B] 装修报价 - 洛谷

我太菜了,还不会,哪怕做了一些位运算的题,感觉应该要按位考虑,但是完全想不出来正确解法,下面是赛时做的dfs暴力解法,大概有30%的分

#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long LL;
const int N = 100010;
const int mod = 1e9 + 7;
int n;
LL ans;
LL a[N];
int op[N];

void dfs(int u)
{
    if (u == n)
    {
        LL t = a[1];
        for (int i = 1; i < n; i ++ )
        {
            if (op[i] != 3 && op[i + 1] == 3)
            {
                LL res = a[i + 1];
                int j = i + 1;
                while (j + 1 < n && op[j + 1] == 3) res ^= a[ ++ j];
                if (op[i] == 1) t += res;
                else t -= res;
                i = j;
                continue;
            }
            else
            {
                if (op[i] == 1) t += a[i + 1];
                else if (op[i] == 2) t -= a[i + 1];
                else t ^= a[i + 1];
            }
        }
        ans = (ans + t) % mod;
        return;
    }
    for (int i = 1; i <= 3; i ++ )
    {
        op[u] = i;
        dfs(u + 1);
    }
}

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i ++ ) cin >> a[i];
    dfs(1);
    cout << ans << endl;
    return 0;
}

正解:贡献法,位运算题目的另外一种常用方法,难怪按位考虑想不出来。 当我们观察样例说明时,容易发现有很多数是相反数,从而相互抵消,而返计算这种相反数是没有意义的,我们考虑何时会产生这种相反数,在一般加减法运算中,将所有的加法换为减法,减法换位加法,就得到了原式的相反数。

在本题中按位异或的优先级大于加减法。因此我们可以将一段连续的异或运算看成一个数字,从而只剩下加法和减法了,而由于是在两个数之间插入运算符,所以第一个数的前面永远可以看成是加法,显然地,只有前缀异或才对答案有贡献,我们只用考虑前缀异或的结果。

特别的,假设前缀异或长度为i,则在第i+1个位置时,只能时+或-,否则前缀异或长度就变为了i+1,从第i+2个位置开始每个位置都有三种情况,假设前缀异或的值为res,那么该情况对答案的贡献就是res * 2 * 3^{(n - (i + 1))}

#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long LL;
const int N = 100010;
const int mod = 1e9 + 7;
int n;
LL ans, res, t = 1;
LL a[N];

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i ++ )
    {
        cin >> a[i];
        res ^= a[i];
    }
    // 此时res为所有数异或的结果
    ans = res;
    res ^= a[n]; // 前n-1个数异或的结果
    // 从前n-1个数开始考虑,不需要每次都使用快速幂计算3的幂次,保证了n-(i-1)>=0, 当然特判一下也可以
    for (int i = n - 1; i >= 0; i -- )
    {
        ans = (ans + res * 2 * t % mod) % mod;
        t = (t * 3) % mod;
        res ^= a[i];
    }
    cout << ans << endl;
    return 0;
}

总结

我还是太菜了,大佬勿喷

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值