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 1≤n≤24, 1 ≤ k ≤ 2 × 10 7 1 \le k \le 2\times 10^7 1≤k≤2×107, 1 ≤ s , m ≤ 10 9 1\le s,m\le 10^9 1≤s,m≤109。
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考级编程题实现、白名单赛事考题实现,记录日常的编程生活、比赛心得,感兴趣的请关注,我后续将继续分享相关内容
用C++实现信奥题 P11069 「QMSOI R1」 生熏鱼&spm=1001.2101.3001.5002&articleId=164163854&d=1&t=3&u=655d5ae7ba0f4688a5dfeeb8004c834f)
741

被折叠的 条评论
为什么被折叠?



