郑州轻工业大学“筑梯杯” 2025级新生程序设计大赛暨省内高校邀请赛——题解

个人主页:星轨初途

个人专栏:C语言数据结构C++学习(竞赛类),C++专栏(开发类),算法及编程题分享


文章目录


难度预估

  • 签到题:1
  • 简单题:3,5,7,12
  • 中档题:2,10,11
  • 困难题:4,9
  • 防AK题:6,8
    总体偏简单,只有后面4道题有难度

一、签到题(1)

1、 真是一场酣畅淋漓的比赛

签到题,直接输出即可

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int main()
{
    cout << "真是一场酣畅淋漓的比赛" << '\n';
    return 0;
}

二、简单题(3,5,7,12)

3、黑白球消除

方法一:模拟

思路

用栈或双端队列模拟即可,这里只展示栈模拟,时间复杂度O(n)

代码
cpp 复制代码
#include <iostream>
#include <stack>
using namespace std;
int main() 
{
    int n;
    cin >> n;
    stack<int> st;
    for (int i = 0; i < n; ++i)
    {
        int x;
        cin >> x;
        // 如果栈不为空,且当前球和栈顶球颜色不同,则消除(出栈)
        if (!st.empty() && st.top() != x)
        {
            st.pop();
        }
        else 
        {
            // 否则入栈
            st.push(x);
        }
    }
    // 栈内剩下的元素个数就是最终答案
    cout << st.size() << "\n";
    return 0;
}

方法二:数学推导

思路

不要被题目中"相邻"和"靠拢"的障眼法骗了!仔细思考消除的本质:每次消除,必然会同时带走 1 个黑球和 1 个白球。

这就意味着,无论黑白球是怎么排列的,也无论你按什么顺序去消除,黑球和白球的数量差值永远不会改变。最终无法消除的时候,剩下的必定全是数量较多的那种颜色的球。

因此,我们根本不需要真的去模拟消除过程,只需要统计出一开始黑球(l)和白球(r)各自的总数,然后求它们的绝对值差 abs(l - r) 即可。时间复杂度O(n) ,空间复杂度极度压缩至 O(1)!

代码
cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
int main()
{
    int n;
    cin>>n;
    int l=0,r=0;
    for(int i=1;i<=n;++i)
    {
        int x;
        cin>>x;
        if(x==1) l++;
        else r++;
    }
    cout<<abs(l-r);
    return 0;
}

5、外卖骑手的赚钱计划(分类讨论 + 贪心/数学思维题)

思路

这道题的关键在于比较"卖出积分能赚多少钱 ( a a a)"与"买回积分需要花多少钱 ( b b b)"。根据 a a a 和 b b b 的大小关系,可以分为两种情况:

情况一:存在套利空间 ( a > b a > b a>b)

  • 逻辑 :既然卖出一个积分赚的钱 ( a a a),比买回一个积分花的钱 ( b b b) 还要多,说明只要手里有 1 1 1 个积分,就可以无限次地"卖出 → \rightarrow → 买入",每次白嫖 a − b a - b a−b 元的差价,直到攒够 n n n 元。
  • 结论 :万事开头难,我们只需要老老实实跑 1 1 1 单 ,拿到初始的 1 1 1 个积分作为"启动资金",之后就能靠无限套利达到任意金额。所以答案恒为 1 1 1

情况二:无套利空间 ( a ≤ b a \le b a≤b)

  • 逻辑:卖积分的收益甚至不够再买回来(或者刚好扯平),此时任何"用钱买积分"的操作都是亏本的。
  • 最佳策略 :放弃花里胡哨的操作,老老实实跑单。每跑一单获得 1 1 1 积分,直接兑换成 a a a 元(相当于每单固定赚 a a a 元)。
  • 结论 :总共需要赚 n n n 元,每单赚 a a a 元,所需单数就是 n n n 除以 a a a 向上取整。在 C++ 中,向上取整的经典写法是 (n + a - 1) / a

代码

基于上面的思路,代码可以直接用一个 if-else 解决:

cpp 复制代码
#include <iostream>
using namespace std;
void cm() 
{
    long long n, a, b;
    cin >> n >> a >> b;
    if (a > b)
     {
        // 只要能套利,跑1单拿到本金即可
        cout << 1 << "\n";
    } 
    else
     {
        // 不能套利,老老实实按单算,向上取整
        cout << (n + a - 1) / a << "\n";
    }
}

int main() 
{
    int t;cin>>t;
    while(t--)cm();
    return 0;
}

7、小猫老弟

思路

这是一道极其经典的线性扫描基础题。题目的本质是求"最长严格连续上升子数组"的长度。

维护两个变量:当前长度 tmp历史最大长度 ans(初始均为 1)。

  1. 若递增 (a[i] > a[i-1]) :状态延续,tmp++
  2. 若中断 (a[i] <= a[i-1]) :结算最大值 ans = max(ans, tmp),并重置 tmp = 1 重新计数。
  3. ⚠️ 必须收尾 :循环结束后,务必再执行一次 ans = max(ans, tmp)(防止最长的递增段刚好在数组末尾,没触发中断)。

代码

时间复杂度O(n)

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10; // 根据题目 n 的最大值 10^5 开数组,多开 10 个防越界
int a[N];           // 全局大数组,避免爆栈
int main()
{
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> a[i]; // 读入 n 天的数字
	int ans = 1; // ans:记录历史出现过的最大连续长度(最终答案)
	int cnt = 1; // cnt:记录当前正在延续的上升段长度(临时答案)
	// 从第 2 个数字开始,依次和前一个数字比较
	for (int i = 2; i <= n; i++) 
	{
		if (a[i] > a[i - 1]) // 状态延续:严格递增
		{
			cnt++;           // 当前上升段长度 +1
		}
		else                 // 状态中断:不再递增
		{
			ans = max(ans, cnt); // 结算:更新历史最大值(打擂台)
			cnt = 1;             // 重置:从当前数字重新开始计算长度
		}
	}
	// 关键收尾:防止最长的一段正好在数组末尾,没机会进 else 触发结算
	ans = max(cnt, ans); 
	cout << ans << '\n'; // 输出最终最长长度
	return 0;
}

12、万年老二

思路

因为没有重复的值,所以对于所有的成绩,可以直接从小到大排序,输出倒数第二个人的成绩即可

代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int main() 
{
    int T;
    cin >> T;
    while (T--)
    {
        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] << '\n';
    }
    return 0;
}

三、中档题(2,10,11)

2、这道题太难了,换成"简单题"了

方法一:暴力枚举

思路

直接枚举最后一位与每一位偶数交换的情况,找到最大的即可,时间复杂度为O(T*n2),够用

代码
cpp 复制代码
#include <iostream>
#include <string> 
using namespace std;
int main() 
{
    int t; cin >> t;
    while (t--)
    {
        string s; cin >> s;
        int len = s.size();
        char a = s[len - 1]; // 记录最后一位的字符
        
        string t;      // 记录打擂台过程中,数值(字典序)最大的合法结果
        int flag = 0;  // 标记是否发生过有效交换
        
        // 遍历前面的所有位,寻找偶数
        for (int i = 0; i < len - 1; i++)
        {
            if ((s[i] - '0') % 2 == 0) // 发现偶数
            {
                // 1. 尝试交换:把偶数换到最后一位
                char b = s[i];
                s[i] = a;
                s[len - 1] = b;
                // 2. 记录或更新最大值
                if (flag == 0)
                {
                    t = s;
                    flag = 1;
                }
                else
                {
                    if (s > t) t = s; // 打擂台:如果当前交换出的数字更大,就替换掉历史最大值
                }
                // 3. 回溯复原:把位置换回来,不影响下一次寻找其他偶数
                s[i] = b;
                s[len - 1] = a;
            }
        }
        // 输出结果
        if (flag == 0) cout << -1 << '\n'; // 一次都没换过,说明前面全是奇数
        else cout << t << '\n';            // 输出打擂台得到的最大结果
    }
    return 0;
}

方法二:贪心

思路

高位替换比低位替换更优。寻找比最后一位小的偶数进行替换,若找不到则找最后一个偶数替换,时间复杂度O(T*n)

代码
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

void cm() 
{
    string s;
    cin >> s;
    int n = s.size();
    
    // 贪心策略 1:想让数字变大,优先动高位
    // 从左往右找,只要发现比最后一位"小"的偶数,换到前面绝对稳赚
    for (int i = 0; i < n - 1; i ++ ) 
    {
        int u = s[i] - '0';
        if (u % 2 == 0 && u < (s[n - 1] - '0')) 
        {
            swap(s[i], s[n - 1]); // 稳赚的交换,直接出结果
            cout << s << '\n';
            return;
        }
    }
    
    // 贪心策略 2:如果前面所有的偶数都比最后一位大,说明怎么换都会亏
    // 为了"把损失降到最低",我们从右往左找,动尽量靠后的偶数
    for (int i = n - 2; i >= 0; i -- ) 
    {
        int u = s[i] - '0';
        if (u % 2 == 0)
        {
            swap(s[i], s[n - 1]); // 止损的交换
            cout << s << '\n';
            return;
        }
    }
    
    // 贪心策略 3:扫了两遍都没找到偶数,无解
    cout << -1 << '\n';
}
int main() 
{
    int t;
    cin >> t;
    while (t -- ) cm();
    return 0;
}

10、幸运数字

方法一:直接乘6进位

思路

对于给出的数组,每一位乘以6,然后按照二进制规则进行进位处理即可。

时间复杂度O(T*N)

代码
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

// 处理单组测试数据的函数
void cm()
{
    string s;
    cin >> s;
    int n = s.size();
    vector<int> a;
    
    // 1. 逆序存储二进制数:将字符串从后往前遍历,存入数组
    // 这样数组的第0位就是个位(最低位),方便后续的进位操作
    for (int i = n - 1; i >= 0; i--) 
    {
        a.emplace_back(s[i] - '0');
    }
    
    // 2. 核心乘法操作:直接将每一位的值乘以6
    // 这里利用了乘法分配律,先不考虑进位,将每个数位都扩大6倍
    for (int i = 0; i < n; i ++ )
    {
        a[i] *= 6;
    }
    
    // 3. 处理进位:二进制下逢二进一
    int k = 0; // k用来存储进位值
    for (int i = 0; i < n; i ++ ) 
    {
        a[i] += k;     // 当前位加上来自低位的进位
        k = a[i] / 2;  // 计算向高位的进位值(因为是二进制,所以除以2)
        a[i] %= 2;     // 当前位保留除以2的余数(0或1)
    }
    
    // 4. 处理最高位剩下的进位
    // 如果遍历完原数字的所有位后,还有进位剩下,则继续向更高位扩展
    while (k) 
    {
        a.push_back(k % 2); // 把进位的最低位追加到数组末尾(即更高位)
        k /= 2;             // 进位右移
    }
    
    // 5. 逆序输出结果:因为数组末尾存的是最高位,所以需要倒序输出
    for (int i = a.size() - 1; i >= 0; i -- ) {
        cout << a[i];
    }
    cout << '\n'; // 换行,准备下一组测试用例的输出
}
int main()
{
    int T;
    cin >> T; // 读取测试样例组数
    while (T-- ) 
    {
        cm(); // 逐组处理
    }
    return 0;
}

方法二:拆分移位相加

思路

利用 6 = 4 + 2:将原数组左移两位(×4)得到数组1,左移一位(×2)得到数组2,最后将两数组对应位相加并进位。

代码

时间复杂度O(T*N)

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
void cm()
{
    string s;
    cin >> s;

    // 1. 移位操作:得到 x*4 和 x*2 的字符串形式
    string s4 = s + "00"; // 原数左移两位 (即 x * 4)
    string s2 = s + "0";  // 原数左移一位 (即 x * 2)

    // 2. 准备高精度二进制加法
    int p1 = s4.size() - 1; // 指向 s4 的最低位
    int p2 = s2.size() - 1; // 指向 s2 的最低位
    int carry = 0;            // 记录进位
    string ans = "";          // 记录结果

    // 3. 从最低位开始,逐位相加
    // 只要两个字符串还没遍历完,或者还有进位,就继续循环
    while (p1 >= 0 || p2 >= 0 || carry > 0)
    {
        // 取出当前位的值,如果越界了(指针小于0)就当作 0
        int bit1 = (p1 >= 0) ? s4[p1] - '0' : 0;
        int bit2 = (p2 >= 0) ? s2[p2] - '0' : 0;

        // 计算当前位的总和
        int sum = bit1 + bit2 + carry;

        // 二进制逢二进一
        ans += to_string(sum % 2); // 当前位的结果
        carry = sum / 2;           // 产生的新进位

        // 指针前移,处理更高位
        p1--;
        p2--;
    }

    // 4. 因为是从低位向高位依次计算并追加到 ans 末尾的,所以最后需要反转字符串
    reverse(ans.begin(), ans.end());

    cout << ans << '\n';
}

int main() 
{
    int T;
    cin >> T;
    while (T--)
    {
        cm();
    }
    return 0;
}

11、整齐划一

思路

核心结论:使数组所有元素变为同一数代价最小的数是中位数。

代码

时间复杂度O(T*N)

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int main() 
{
    int T;
    cin >> T;
    while (T--) 
    {
        int n;
        cin >> n;
        long long ans = 0;
        vector<int> a(n);
        for (int i = 0; i < n; i++) 
        {
            cin >> a[i];
        }
        for (int i = 0; i < n; i ++ ) 
        {
            ans += abs(a[i] - a[n / 2]);
        }
        cout << ans << '\n';
    }
    return 0;
}

四、困难题(4,9)

4、跑步传递

思路

  • 左侧情况: 速度快的人超过已知密码者会获知密码。
  • 右侧情况: 速度慢的人被速度快的人赶上会获知密码。
  • 解题策略: 统计左边最快速度v1和右边最慢速度v2。右边速度<v1或左边速度>v2的人均可获知密码,统计符合条件的人数。

代码

时间复杂度O(N)

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

void cm() 
{
    int n, k;
    cin >> n >> k;
    k --; // 将第 k 个人转化为数组下标 (0-based)
    
    // a[i][0] 存初始位置,a[i][1] 存速度
    vector<vector<int>> a(n, vector<int>(2));
    for (int i = 0; i < n; i ++ ) cin >> a[i][0];
    for (int i = 0; i < n; i ++ ) cin >> a[i][1];
    
    int d = a[k][0]; // 感染源(已知密码者)的初始位置
    int l = a[k][1]; // l对应PPT的 v1:左侧(含同起点)的最快速度
    int r = a[k][1]; // r对应PPT的 v2:右侧(含同起点)的最慢速度
    
    // 第一遍扫描:找左侧极速,右侧龟速
    for (int i = 0; i < n; i ++ )
    {
        if (a[i][0] <= d) l = max(l, a[i][1]); 
        if (a[i][0] >= d) r = min(r, a[i][1]); 
    }
    
    int ans = 0;
    // 第二遍扫描:利用极值判断谁能被传染
    for (int i = 0; i < n; i ++ ) 
    {
        // 位于左侧:只要你的速度 > 右侧最慢的(r),你迟早能追上他并拿到密码
        if (a[i][0] < d && a[i][1] > r) ans ++;
        
        // 恰好同起点:开局贴脸直接拿到密码
        if (a[i][0] == d) ans ++;
        
        // 位于右侧:只要你的速度 < 左侧最快的(l),你迟早会被他赶上并拿到密码
        if (a[i][0] > d && a[i][1] < l) ans ++;   
    }
    
    cout << ans << '\n';
}

int main()
{
    int T;
    cin >> T;
    while (T-- ) cm();
    return 0;
}

9、zzh的背包问题

思路

这是一道极其经典的**"反套路"背包题**。

如果在平时,看到"背包问题"四个字,绝大多数人的第一反应是写动态规划(DP)。但是这道题的背包容量 N N N 高达 10 9 10^9 109,如果开一个这么大的 DP 数组,绝对会直接爆内存(MLE)。

但这道题给了一个超级突破口:物品永远只有 10 件!

这就意味着所有的拿法组合只有 2 10 = 1024 2^{10} = 1024 210=1024 种。所以,放弃复杂的 DP,直接用**二进制状态压缩(位运算暴力枚举)**才是最完美的降维打击!

代码

O ( T ) O(T) O(T)

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

void cm()
{
    int N;
    cin >> N;
    vector<int> a(10), b(10);
    for (int i = 0; i < 10; i ++ ) cin >> a[i]; // 读入10件物品的体积
    for (int i = 0; i < 10; i ++ ) cin >> b[i]; // 读入10件物品的价值
    
    int ans = 0;
    
    // 核心:二进制枚举(状态压缩)。10件物品共有 2^10 = 1024 种拿法
    // 变量 i 会从 0 遍历到 1023,它的二进制刚好可以代表所有"拿与不拿"的组合
    // 例如 i = 3 (二进制 0000000011),代表只拿第 0 件和第 1 件
    for (int i = 0; i < (1LL << 10); i ++ )
    {
        int cost = 0; // 当前组合的总花费(体积)
        int val = 0;  // 当前组合的总价值
        
        // 遍历这 10 个位,看看当前组合 i 到底拿了哪些物品
        for (int j = 0; j < 10; j ++ ) 
        {
            // 判断组合 i 的第 j 位是否为 1(即第 j 件物品是否被选中)
            if (i >> j & 1) 
            {
                cost += a[j]; // 累加体积
                val += b[j];  // 累加价值
            }
        }
        
        // 如果当前组合的总体积装得下背包,就尝试更新最大价值(打擂台)
        if (cost <= N) 
        {
            ans = max(ans, val);
        }
    }
    cout << ans << '\n';
}

int main() 
{
    int T;
    cin >> T;
    while (T--)
    {
        cm();
    }
    return 0;
}

五、防AK题(6,8)

6、车间加工

思路

正面去模拟加工过程显然是不现实的,数据范围极大。我们仔细思考:在什么情况下才会发生"连续加工"?

答案是:只有在加工的最后阶段,别人都做完退出了,场上只剩下一个零件最多的人(卷王)时,才可能发生连续加工。

设最大的零件数为 M M M。我们统计最大值的个数 c c c,和次大值( M − 1 M-1 M−1)的个数 c n t cnt cnt。

  • 情况一 :如果 c ≥ 2 c \ge 2 c≥2,说明最后阶段至少有两个人轮流干活,绝对不可能连续加工。所有 n ! n! n! 种排列都合法!
  • 情况二(核心) :如果 c = 1 c = 1 c=1,卷王只有一个。如果他在倒数第二轮是最后一个干活的,紧接着最后一轮又轮到他,就会违规。

怎么避免违规?利用相对位置!

在倒数第二轮,场上只剩下卷王和 c n t cnt cnt 个次大数的人(共 c n t + 1 cnt + 1 cnt+1 个人),其他人都已经退出了。

我们不需要关心全排列里其他人怎么排,只要在这 c n t + 1 cnt + 1 cnt+1 个人的相对顺序 中,卷王不排在最后一位即可!

在这 c n t + 1 cnt + 1 cnt+1 个人里,卷王排在最后一位的概率是 1 c n t + 1 \frac{1}{cnt + 1} cnt+11。

所以,合法的排列占比就是 c n t c n t + 1 \frac{cnt}{cnt + 1} cnt+1cnt。

代码

总方案数 = 总排列数 × \times × 合法比例。核心公式仅仅是: n ! ⋅ c n t c n t + 1 \frac{n! \cdot cnt}{cnt + 1} cnt+1n!⋅cnt。

这里因为n!一定包含(cnt+1),所以不用求逆元,直接相除即可

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 998244353;
int main()
{
    // 优化输入输出流 
    ios::sync_with_stdio(0);
    cin.tie(0);
    int n;
    cin >> n;
    
    // 空间 O(1) 优化:边读边维护最大值、次大值及其出现次数
    int mx1 = 0, mx2 = 0, cnt1 = 0, cnt2 = 0;
    
    for(int i = 0; i < n; ++i)
    {
        int t;
        cin >> t;
        
        if(t > mx1)             // 发现新的最大值
        {
            mx2 = mx1;          // 老大退居老二
            cnt2 = cnt1;
            mx1 = t;            // 新老大上位
            cnt1 = 1;
        }
        else if(t == mx1)       // 和最大值一样大
            cnt1++;
        else if(t < mx1 && t > mx2) // 夹在老大和老二之间
        {
            mx2 = t;            // 替换老二
            cnt2 = 1;
        }
        else if(t == mx2)       // 和次大值一样大
            cnt2++;
    }
    
    ll ans = 1;
    
    // 情况一:卷王不止一个,随便排都行,答案为 n!
    if(cnt1 > 1)
    {
        for(int i = 1; i <= n; ++i)
            ans = ans * i % mod;
    }
    // 情况二:卷王只有 1 个,且存在能帮他挡枪的次大值(相差为1)
    // 核心公式:N! * cnt2 / (cnt2 + 1)
    else if(mx1 == mx2 + 1)
    {
        for(int i = 1; i <= n; ++i)
        {
            if(i == cnt2 + 1) 
                continue;       // 精华所在:直接跳过分母,完美约分!
            ans = ans * i % mod;
        }
        ans = ans * cnt2 % mod; // 循环结束后,统一乘上分子
    }
    // 情况三:卷王只有 1 个,且没人能挡枪,必死局
    else 
        ans = 0;
        
    cout << ans;
    return 0;
}

8、你想成为扑克牌高手吗

思路

遇到求"最多能怎样"的问题,第一反应是二分答案 。假设我们要打出 x x x 套牌,总牌数为 A A A,那么二分上限为 ⌊ A / 4 ⌋ \lfloor A / 4 \rfloor ⌊A/4⌋。

重点在于怎么写极快的 check(x)。如果强行分类讨论去模拟挑牌的过程,不仅代码会极度臃肿,还非常容易漏掉边界条件。

直接上数学充要条件推导:

设面值 i i i 被用作"三张同号底牌"的次数为 b i b_i bi。

设面值 i i i 扣除底牌后,留在备用池里的剩余数量为 c i c_i ci(即 c i = a i − 3 b i c_i = a_i - 3b_i ci=ai−3bi)。

设所有面值的剩余数量总和为 C C C(即 C = A − 3 x C = A - 3x C=A−3x)。

你要给面值 i i i 找 b i b_i bi 张单牌,你只能去"别人家"的剩余牌里挑。所以你的需求量,绝不能超过别家牌的总数:
b i ≤ C − c i b_i \le C - c_i bi≤C−ci

移项得到一个极其优雅的充要条件(本质上是霍尔定理的一个特例):
b i + c i ≤ C b_i + c_i \le C bi+ci≤C

将 c i c_i ci 和 C C C 展开代入:
b i + a i − 3 b i ≤ A − 3 x b_i + a_i - 3b_i \le A - 3x bi+ai−3bi≤A−3x
2 b i ≥ a i − A + 3 x 2b_i \ge a_i - A + 3x 2bi≥ai−A+3x
b i ≥ ⌈ a i − A + 3 x 2 ⌉ b_i \ge \lceil \frac{a_i - A + 3x}{2} \rceil bi≥⌈2ai−A+3x⌉

同时,物理上限是 b i ≤ ⌊ a i / 3 ⌋ b_i \le \lfloor a_i / 3 \rfloor bi≤⌊ai/3⌋。

代码

我们只需要分别算出每种牌做底牌的最少次数(下界 X)最多次数(上界 Y)

只要我们需要的总套数 x x x,落在所有上下界的总和区间内,就一定存在合法的分配方案!思维深度极高,代码却异常清爽。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

// 验证:能否打出 x 套三带一
bool check(int x, const vector<int>& a, int sum) 
{
    int n = a.size();
    int R = 0, L = 0; // 记录所有牌作为底牌的总上界和总下界
    
    for (int i = 0; i < n; i++)
    {
        int Y = a[i] / 3; // 上界:最多能凑几套这道面值的底牌
        
        // 下界:最少需要凑几套(不等式推导得出,且必须 >= 0)
        // (val + 1) / 2 是整数除法向上取整的经典技巧
        int X = max(0, (a[i] - sum + 3 * x));
        X = (X + 1) / 2; 
        
        if (X > Y) return false; // 逻辑矛盾:要求的下界比物理上限还大,直接无解
        
        R += Y;
        L += X;
    }
    
    // 只要目标套数 x 落在总的合规区间 [L, R] 内,就必定存在分配方案
    return L <= x && x <= R;
}

void solve() 
{
    int n = 13;
    vector<int> a(n);
    int sum = 0;
    
    for (int i = 0; i < n; i++)
    {
        cin >> a[i];
        sum += a[i]; // 统计总牌数
    }
    
    // 二分查找最大套数:最少 0 套,最多 ⌊总牌数/4⌋ 套
    int l = 0, r = sum / 4;
    while (l < r)
    {
        int mid = (l + r + 1) >> 1; // 向上取整,配合 l = mid 防止死循环
        
        if (check(mid, a, sum))
        {
            l = mid;    // mid 方案可行,尝试找更大的
        } 
        else 
        {
            r = mid - 1; // mid 方案不行,缩小上限
        }
    }
    
    cout << l << "\n";
}

int main()
{
    int T;
    cin >> T;
    while (T--) solve();
    return 0;
}

结束语

本篇到这里就结束啦,欢迎大家讨论和建议,感谢大家的支持啦!

相关推荐
淮南颂恩少儿编程2 小时前
淮南少儿编程 | CSP-J真题详解:在淮南也有接地气的算法课
c++·人工智能·python·深度学习·算法·青少年编程·蓝桥杯
zh路西法2 小时前
【宇树机器人强化学习】(五):go2奖励函数的实现与模型检验
python·深度学习·算法·机器学习·机器人
m0_748873552 小时前
模板编译期排序算法
开发语言·c++·算法
资深流水灯工程师2 小时前
BMI270应用笔记1:BMI270结构体详解
笔记
2401_842623652 小时前
基于C++的爬虫框架
开发语言·c++·算法
无限进步_2 小时前
【C++】获取字符串最后一个单词长度的多种解法
开发语言·c++·ide·windows·git·github·visual studio
黄林晴2 小时前
Android内核引入AuroFDO,你的App变快了
android
Wpa.wk2 小时前
自动化测试 - Playwrigh简单介绍+基础使用
经验分享·测试工具·playwright
沈阳信息学奥赛培训2 小时前
#define 和 typedef 的区别
开发语言·c++