洛谷蓝桥杯模拟赛


A:P11995 在小小的日历里面数呀数呀数(填空题)

P11995 在小小的日历里面数呀数呀数 - 洛谷

题目描述

在这里插入图片描述
题目描述

核心思路

这里我们很容易想到下一个完全平方年是 46^2,也就是 2116 年,直接枚举日期,判断日期是否合法,再统计有效日期,不过要注意从 20250330 开始枚举!

代码实现

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

int days[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

// 判断一个日期是否有效
bool is(int date)
{
	int year = date / 10000;
	int month = date / 100 % 100;
	int day = date % 100;
	
	if (month == 0 || month > 12) return false;
	
	if (month != 2 && (day == 0 || day > days[month])) return false;
	if (month == 2)
	{
		int leap = year % 100 && year % 4 == 0 || year % 400 == 0;
		if (day == 0 || day > days[2]+leap ) return false;
	}
	return true;
}

int main()
{
    int res = 0;
    for (int i = 20250330; i <= 21160101; i++)
    	if (is(i)) res++;
    
	cout << res;
	return 0;
}

B:P11996 我是黄色恐龙大将军(填空题)

P11996 我是黄色恐龙大将军 - 洛谷

题目描述

题目描述

核心思路

计算 2^i5^i,然后提取这两个数的最高非零数字,把这两个数字相乘。把每次得到的乘积存入一个去重的集合中,确保每个乘积只算一次。最后把集合中所有不同的乘积加起来得到一个总和。因为是填空题,所以我们只要计算到某个合适的比较大的指数,之后结果就不再变化了。

代码实现

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

typedef long long ll;

// 提取数字的最高非零位
int get(ll x)
{
	while (x >= 10) x /= 10;
    return x;
}

signed main()
{
    set<int> res;  // 存储去重后的结果
    
    // 计算2^i和5^i的最高非零位的乘积
    for (int i = 1; i <= 500000; i++)
    {
    	ll x = pow(2, i);
    	ll y = pow(5, i);
    	res.insert(get(x) * get(y));
	}
	
	// 计算所有不同值的和
	int sum = 0;
    for (int i : res) sum += i;
	
	cout << sum;
	
	return 0;
}

C:P11997 化食欲为动力

P11997 化食欲为动力 - 洛谷

题目描述

小蓝去超市买早餐,他需要选一个面包、一包火腿肠和一盒牛奶。每个商品都有一个食欲值,吃完这顿早餐后,他的动力值等于(面包的食欲 × 火腿肠的食欲) % 牛奶的食欲。比如选第i个面包(食欲a[i])、第j包火腿肠(食欲b[j])和第u盒牛奶(食欲c[u]),动力值为 (a[i] × b[j]) % c[u]。题目要求找出所有可能的组合中,小蓝能得到的最大动力值。

核心思路

通过三重循环暴力穷举所有组合,计算 (a[i] × b[j]) % c[u],然后记录最大的结果。

代码实现

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define IOS ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);

const int N = 205;
int n, m, k;
int a[N], b[N], c[N];

signed main() {
    IOS;
    cin >> n >> m >> k;
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i <= m; i++) cin >> b[i];
    for (int i = 1; i <= k; i++) cin >> c[i];

    int max_power = 0;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            for (int u = 1; u <= k; u++)
                max_power = max(max_power, (a[i] * b[j]) % c[u]);

    cout << max_power << endl;
    return 0;
}

D:P11998 哇,这就是 5p

P11998 哇,这就是 5p - 洛谷

题目描述

给定一个答题比赛,有 n 道题,你可以做对或做错。

  • 做对:你可以得到 a[i] 分,概率是 p[i]
  • 做错:你得 0 分,概率是 (1 - p[i])
    我们要计算 最终总分是 m 的倍数的概率,并且 对 998244853 取模,防止数字过大。

🌟核心思路

如果暴力枚举所有得分组合,可能的得分组合太多,会超时。我们可以只关心得分除以 m 的余数,因为 只要余数是 0,就说明是 m 的倍数

具体步骤

  1. 输入处理:读取 n 和 m,然后读取 a 数组和 p 数组。对 a 数组中的每个元素取模 m,减少后续计算的复杂度。
  2. 初始化DP数组:创建一个大小为 m 的数组 f,初始时 f[0] = 1,其余为0。
  3. 遍历每一道题
    • 对于每道题i,创建一个临时数组dp,初始化为0。
    • 计算做错的概率 q = (1 - p[i] + MOD) % MOD。
    • 遍历当前所有可能的余数 j ,如果 f[j] 不为0,则分别处理做对和做错的情况,更新dp数组。
  4. 更新 f 数组:将 dp 数组复制到 f 数组中,处理下一道题。
  5. 输出结果:最终f[0]即为总分是m倍数的概率,输出其对MOD取模的值。

状态表示
我们用 f[j] 记录:

当前情况下,得分模 m 余数为 j 的概率

初始状态:

  • f[0] = 1(因为刚开始没做题,总分为 0,所以余数 0 的概率是 100%)。
  • 其他 f[j] = 0(因为没做题,不可能有别的余数)。

状态计算
每道题有 两种情况

  1. 做对了
    • 你的分数 增加 a[i],余数变成 (j + a[i]) % m,概率要 乘上 p[i]
    • 更新公式: dp[next] += f[j] * p[i]
  2. 做错了
    • 你的分数 不变,余数还是 j,概率要 乘上 (1 - p[i])
    • 更新公式: dp[j] += f[j] * (1 - p[i])

为了防止状态被覆盖,我们用一个 临时数组 dp[m] 来存储新的状态,计算完后再更新到 f[m] 里。这里注意题目中给出的 p[i] 是已经对 998244853 取模后的值。在代码中,做错题的概率是1-p[i],但由于 p[i] 是模后的值,直接计算 1 - p[i] 可能会导致负数,所以需要用 (1 - p[i] + MOD) % MOD 来确保结果非负。

📌代码实现

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

#define int long long  
#define IOS ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0)  

const int N = 100010, MOD = 998244853;  

int n, m;
int a[N], p[N];

signed main()
{
    IOS;
    
    cin >> n >> m;
    
    for (int i = 0; i < n; i++) cin >> a[i], a[i] %= m;  // 先取模,减少计算量
    for (int i = 0; i < n; i++) cin >> p[i];

    int f[m] = {0};  // f[j] 表示当前余数 j 的概率
    f[0] = 1;  // 初始状态总分为 0 的概率是 100%

    for (int i = 0; i < n; i++)  // 遍历每道题
    {
        int dp[m] = {0};  // 临时数组
        int q = (1 - p[i] + MOD) % MOD;  // 计算做错的概率,防止负数

        for (int j = 0; j < m; j++)  
        {
            if (f[j] == 0) continue;  // 这个余数概率是 0,跳过计算
            
            int next = (j + a[i]) % m;  // 做对后的新余数
            
            // 做对的情况:概率加到新余数
            dp[next] = (dp[next] + f[j] * p[i]) % MOD;  

            // 做错的情况:概率加到原余数
            dp[j] = (dp[j] + f[j] * q) % MOD;  
        }

        memcpy(f, dp, sizeof dp);  // 更新 f 数组,进行下一轮计算
    }
    
    cout << f[0] << endl;  // 余数为 0 的概率,即答案
    
    return 0;
}

时间复杂度

  • 外层 n 道题,时间 O(n)
  • 内层 m 个余数状态,时间 O(m)
  • 总时间复杂度 O(n × m)

F:P12000 扶苏出勤日记

P12000 扶苏出勤日记 - 洛谷

题目描述

扶苏每天都会赚到一定的钱,这些钱可以用来买游戏币。不过,游戏币的汇率每天都不同——有些天便宜,有些天贵。她希望每天都能玩相同局数的游戏,我们要帮她算出 最多每天能玩多少局

分析一下问题:

  • a[i] 表示第 i 天 1 块钱能买多少个游戏币。
  • b[i] 表示第 i 天扶苏能赚多少钱。
  • 她可以把钱存起来,也可以把游戏币存起来,不一定要当天就用掉。
  • 目标: 让她每天都玩一样多的局数,并且这个局数尽量大。

🌟核心思路

我们可以用 二分答案 来试着找到这个“最多每天玩几局”的答案。

  1. 假设扶苏每天玩 ans 局游戏(每局要 1 个游戏币)。
  2. 能不能做到? 我们检查,如果 ans 局可行,那更小的 ans 也一定可行(因为花的币更少了)。
  3. 检查 ans 是否可行?
    • 按天模拟,如果游戏币不够,就看看能不能用存下来的钱兑换到足够的游戏币。
    • 兑换时,优先用最划算的钱来换,这样能换更多的游戏币。

代码逻辑:

  1. 建立一个队列 q

    • 这个队列里存着 之前所有天赚的钱,并且按 汇率从高到低 排序。
    • 因为我们希望优先使用 高汇率 a[i] 那天的钱,这样能换最多的游戏币。
  2. 每天赚钱的处理

    • 新的一天到了,今天赚了 b[i] 块钱,汇率是 a[i]
    • 看看之前的钱,如果汇率比 a[i] 低,那就合并到今天的钱里(更划算)。
    • 把这笔钱放进 q,保证 q 一直是按照汇率降序排列
  3. 每天消耗 ans 个游戏币

    • s -= ans; 这是每天固定消耗的游戏币。
    • 如果游戏币不够了,就得想办法用存的钱换币。
  4. 怎么兑换游戏币?

    • 直接从 q 最前面 开始兑换,因为 q 里最前面的钱 汇率最高
    • 计算 最多能用这天的钱换多少游戏币,然后减少 need(需要补的币)。
    • 这天的钱如果用完了,就从 q 里删掉,继续从前面天数中 a[i] 最大的一天开始兑换,直到满足 need。
  5. 兑换完后,如果 need > 0,就返回 false

    • 即使按照最高效的策略兑换也无法满足这天玩 ans局游戏的需求
    • 说明我们尝试的 ans 取太大了,无法支撑。
  6. 拥有游戏币的数量 s

    • s 为负数说明缺币。 need = -s; 是我们欠缺的游戏币数量
    • s += max_p * q[0].a 是从负数开始一步步恢复的,如果ans 最终能被满足,那么 s 也会累加成为正确的剩余游戏币数量

📌核心代码

1. 主函数的二分查找

int l = 0, r = 1e18;  // l 是最小可能值(0局),r 是最大可能值(设得很大)
int ans = 0;

while (l <= r) {
    int mid = (l + r) >> 1; // 试试看每天玩 mid 局能不能成功
    if (check(mid)) ans = mid, l = mid + 1; // 能行,看看还能不能更大
    else r = mid - 1; // 不行,降低目标
}

这里我们用 二分法 试着找到每天最多能玩多少局。

  • mid 是当前尝试的局数。
  • check(mid) 是核心函数,检查 如果每天玩 mid 局,最终能不能撑住

为什么右边界是 1e18

题目要求的是 最大每天游戏局数,我们必须确保 r 设得足够大,不会漏掉答案。

数据范围:

  • n 最多 1e6(100万天)
  • b[i] 最大 1e9(每天最多赚 10 亿)
  • a[i] 最大 1000(1 块钱最多买 1000 个币)

如果把所有的钱都换成游戏币,理论上的上限是:

最大总钱数 = 1e6 * 1e9 = 1e15
最大可能兑换币数 = 1e15 * 1e3 = 1e18

2. 检查 mid 是否可行

bool check(int ans) {
    deque<Node> q; // 这个队列存每天的 a[i] 和 b[i],按照汇率 a[i] 降序排序
    int s = 0; // 现有的游戏币数量

    for (int i = 0; i < n; ++i) 
    {
        // 今天新赚到的钱
        Node cur = {a[i], b[i]}; // 今天的汇率 a[i],今天的收入 b[i]
        
        // 把之前汇率比今天低的钱整合进今天的钱,确保队列是按“最高汇率”排序
        while (!q.empty() && q.back().a <= cur.a) 
        {
            cur.money += q.back().money;
            q.pop_back();
        }
        q.push_back(cur); // 把今天的钱存进去

        // 每天固定消耗 ans 个游戏币
        s -= ans;
        if (s < 0) // 如果币不够了,就想办法补足
        {  
            int need = -s; // 需要补的币
            while (need > 0 && !q.empty()) 
            {
                // 计算最多能从当前最高汇率的钱里兑换多少游戏币
                // max_p是买游戏币花费的钱,向上取整确保满足need
                int max_p = min(q[0].money, (need + q[0].a - 1) / q[0].a);
                if (max_p == 0) break; // 兑换不了,直接失败
                
                s += max_p * q[0].a; // 兑换后的币数
                need -= max_p * q[0].a;
                q[0].money -= max_p;
                if (q[0].money == 0) q.pop_front(); // 这个天的钱用完了就删掉
            }
            if (need > 0) return false; // 还是不够,说明 d 取太大了
        }
    }
    return true;
}

📌完整代码

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

const int N = 1e6 + 10;
int n;
int a[N], b[N];

struct Node 
{
    int a, money; // 汇率和剩余钱数
};

bool check(int ans) 
{
    deque<Node> q;
    int s = 0;

    for (int i = 0; i < n; ++i) 
	{
        Node cur = {a[i], b[i]};
        while (!q.empty() && q.back().a <= cur.a) 
		{
            cur.money += q.back().money;
            q.pop_back();
        }
        q.push_back(cur);

        s -= ans;
        if (s < 0) 
		{
            int need = -s;
            while (need > 0 && !q.empty()) 
			{
                int max_p = min(q[0].money, (need + q[0].a - 1) / q[0].a);
                if (max_p == 0) break;

                s += max_p * q[0].a;
                need -= max_p * q[0].a;
                q[0].money -= max_p;
                if (q[0].money == 0) q.pop_front();
            }
            if (need > 0) return false;
        }
    }
    return true;
}

signed main() 
{
    ios_base::sync_with_stdio(false);
    cin.tie(0);
    int T;
    cin >> T;
    while (T--)
    {
    	cin >> n;
	    for (int i = 0; i < n; i++) cin >> a[i];
	    for (int j = 0; j < n; j++) cin >> b[j];
	
	    int l = 0, r = 1e18;
	    int ans = 0;
	
	    while (l <= r) 
		{
	        int mid = (l + r) >> 1;
	        if (check(mid)) ans = mid, l = mid + 1;
	        else r = mid - 1;
	    }
	    cout << ans << endl;
	}
    return 0;
}

时间复杂度

  • 二分法部分:最多 log2(1e18) ≈ 60 次。
  • 每次 check(d) 运行时间O(n)(因为队列里的每个元素最多入队出队一次)。
  • 总复杂度O(T * n log C)(这里 C = 1e18)。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值