打卡信奥刷题(3538)用C++实现信奥题 P11069 「QMSOI R1」 生熏鱼

P11069 「QMSOI R1」 生熏鱼

题目背景

一切起源于一个叫神荀彧的武将…

那这道题与神荀彧的关系在哪里呢?

题目描述

一共有 n n n 种攻击,第 i i i 种攻击会先让你得到 a i a_i ai 点经验,然后让你失去 b i b_i bi 点血量。

你将依次受到 k k k 次攻击,其中,第 i i i 次攻击的种类是 c i c_i ci,你的初始血量为 m m m

为了获得更多的经验,你可以选择 n n n 种攻击中的任意种,并防止你受到的第一次这种攻击,防止后既不会损失血量,也不会增加经验值。

现在你想知道的是在你的血量降到 0 0 0 及以下前,最多能获得多少点经验。

输入格式

一行 4 个整数,分别代表 n , m , k , s n,m,k,s n,m,k,s,其中, s s s 为随机种子,其它变量含义与题目描述相同。

因为本题输入数据过大,选手需要使用如下方式获取数据:

const int M=1e9,C=1e5+5;
void read(){
    cin>>n>>m>>k>>s;
    mt19937 rand(s);
    for(int i=1;i<=n;i++)  a[i]=rand()%M+1,b[i]=rand()%C+1;
    for(int i=1;i<=k;i++)  c[i]=rand()%n+1;
}

数组含义与题目描述中相同。

输出格式

输出一行一个整数,表示你最多能获得多少点经验。

输入输出样例 #1

输入 #1

2 100000 5 114514

输出 #1

3765807592

说明/提示

样例解释

样例 1 1 1 的数据中 a = { 953888980 , 904140652 } , b = { 6583 , 80624 } , c = { 1 , 2 , 1 , 1 , 2 } a=\{953888980,904140652\},b=\{6583,80624\},c=\{1,2,1,1,2\} a={953888980,904140652},b={6583,80624},c={1,2,1,1,2}

此时显然可以不防止任何攻击或者防止第一次类型 2 2 2 的攻击获得 953888980 × 3 + 904140652 = 3765807592 953888980\times 3+904140652=3765807592 953888980×3+904140652=3765807592 的经验值。

可以证明,不存在获得经验值更多的方案。

数据范围

本题使用 subtask 进行捆绑测试,每个 subtask 的具体分值如下:

子任务 n n n k k k分值
0 0 0 ≤ 10 \le 10 10 ≤ 10 3 \le 10^3 103 20 20 20
1 1 1 ≤ 20 \le 20 20 ≤ 10 7 \le 10^7 107 30 30 30
2 2 2 ≤ 24 \le 24 24 ≤ 2 × 10 7 \le 2\times 10^7 2×107 50 50 50

对于所有数据,满足 1 ≤ n ≤ 24 1\le n \le 24 1n24 1 ≤ k ≤ 2 × 10 7 1 \le k \le 2\times 10^7 1k2×107 1 ≤ s , m ≤ 10 9 1\le s,m\le 10^9 1s,m109

C++实现

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 30, maxk = 2e7 + 5;
const int M = 1e9, C = 1e5 + 5;
int n, m, k, s;
int a[maxn], b[maxn], c[maxk];
ll dp[C * maxn], suf[C * maxn]; 
bool vis[maxn]; // vis[i]表示第i种攻击是否出现过
void solve(){
	cin>>n>>m>>k>>s;
	mt19937 rand(s);
	for(int i=1;i<=n;i++)  a[i]=rand()%M+1,b[i]=rand()%C+1;
	for(int i=1;i<=k;i++)  c[i]=rand()%n+1;
	memset(dp, 0x3f, sizeof dp);
	memset(suf, 0x3f, sizeof suf);
	dp[0] = 0;
	ll sum = 0, maxn = 0; // s统计经验前缀和、maxn统计能获得的最大经验
	for(int i = 1; i <= k; i ++){
		if(!vis[c[i]]){ // 如果这是第c[i]种攻击第一次出现
			vis[c[i]] = 1;
			for(int j = C * n; j >= b[c[i]]; j --){ // 注意滚动数组要倒着遍历,避免提前计算
				dp[j] = min(dp[j], dp[j - b[c[i]]] + a[c[i]]);
			}
			for(int j = C * n; j >= 0; j --){
				suf[j] = min(suf[j + 1], dp[j]);
			}
		}
		sum += a[c[i]];
		if(m <= 0){
			if(1 - m <= C * n && suf[1 - m] != LLONG_MAX){
				maxn = max(maxn, sum - suf[1 - m]);
			}
			else break; // 否则说明救不回来了,退出循环即可
		}
		else{ 
			maxn = max(maxn, sum);
		}
		m -= b[c[i]];
	}
	cout << maxn << '\n';
}
signed main()
{
	solve();
	return 0;
}

在这里插入图片描述

后续

接下来我会不断用C++来实现信奥比赛中的算法题、GESP考级编程题实现、白名单赛事考题实现,记录日常的编程生活、比赛心得,感兴趣的请关注,我后续将继续分享相关内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值