题解:洛谷 P11247 算法学习
【题目来源】
洛谷:P11247 [GESP202409 六级] 算法学习 - 洛谷
【题目描述】
小杨计划学习\(m\) 种算法,为此他找了\(n\) 道题目来帮助自己学习,每道题目至多学习一次。
小杨对于\(m\)种算法的初始掌握程度均为\(0\)。第\(i\) 道题目有对应的知识点\(a_i\) ,即学习第\(i\) 道题目可以令小杨对第\(a_i\) 种算法的掌握程度提高\(b_i\)。小杨的学习目标是对\(m\) 种算法的掌握程度均至少为 \(k\)。
小杨认为连续学习两道相同知识点的题目是不好的,小杨想请你编写程序帮他计算出他最少需要学习多少道题目才能使得他在完成学习目标的同时避免连续学习两道相同知识点的题目。
【输入】
第一行三个正整数\(m\),\(n\),\(k\),代表算法种类数,题目数和目标掌握程度。
第二行\(n\)个正整数\(a_1,a_2,\dots, a_n\),代表每道题目的知识点。
第二行\(n\)个正整数\(b_1,b_2,\dots, b_n\),代表每道题目提升的掌握程度。
【输出】
输出一个整数,代表小杨最少需要学习题目的数量,如果不存在满足条件的方案,输出\(-1\)。
【输入样例】
3 5 10
1 1 2 3 3
9 1 10 10 1
【输出样例】
4
【核心思想】
-
问题分析:给定 \(m\) 种算法、\(n\) 道题目,每道题目属于知识点 \(a_i\),学习后提升 \(b_i\)。要求每种算法掌握程度均 \(\geq k\),且不能连续学习两道相同知识点的题目。求最少学习题目数,或判断无解。这是一个贪心 + 可行性判定问题,核心在于先计算每类最少需要多少题,再判断能否通过交替学习避免连续同类。
-
算法选择:
- 每类贪心选取:对每类题目按 \(b_i\) 降序排序,取前若干个使总和 \(\geq k\),记录每类最少需要 \(need[i]\) 题
- 交替可行性判定:将需要最多题目的类别作为"主类别",判断其余类别能否提供足够的题目进行交替穿插
-
关键步骤:
- 读入数据:读取 \(m, n, k\),知识点 \(a[1..n]\),提升值 \(b[1..n]\)
- 分类存储:将题目按知识点分类,\(score[i]\) 存储知识点 \(i\) 的所有提升值
- 每类排序选取(\(i\) 从 \(1\) 到 \(m\)):
- 对 \(score[i]\) 降序排序
- 贪心累加前 \(j\) 个最大值,直到 \(\geq k\),记录 \(need[i] = j\)
- 若全部累加仍 \(< k\):输出 \(-1\) 并结束
- 统计与判定:
- \(ans = \sum need[i]\),\(mx\_need = \max need[i]\),\(mx\_need\_i\) 为对应类别
- 情况1:若 \(mx\_need - 1 \leq ans - mx\_need\):说明其他类别题目足够穿插,输出 \(ans\)
- 情况2:计算其他类别的额外题目数 \(last = \sum_{i \neq mx\_need\_i}(score[i].size() - need[i])\)
- 若 \(mx\_need - 1 \leq ans - mx\_need + last\):输出 \(2 \times mx\_need - 1\)(主类别最多连续出现 \(mx\_need\) 次,需 \(mx\_need - 1\) 个间隔)
- 否则输出 \(-1\)
-
时间/空间复杂度:
- 时间复杂度:\(O(n \log n)\),排序主导
- 空间复杂度:\(O(n)\),分类存储
-
贪心选取与交替判定的核心思想:
- 每类独立贪心:每类题目按提升值降序选取,确保达到 \(k\) 所需题目数最少,这是局部最优
- 交替的鸽巢原理:将需要最多题目的类别视为"高频元素",其余类别视为"间隔元素"。要避免连续同类,高频元素之间必须有足够的间隔元素
- 两种策略:第一种策略仅使用每类最少需要的题目进行交替;若不够,第二种策略利用其他类别的额外题目(超过 \(k\) 所需的部分)作为间隔
- 最坏情况公式:当主类别需要 \(mx\_need\) 题时,最坏需要 \(2 \times mx\_need - 1\) 个位置(间隔排列),若可用位置足够则可行
- 适用于带约束的资源分配、交替排列可行性判定类贪心问题
【算法标签】
普及 #贪心
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5; // 定义最大数量
const int inf = 0x3f3f3f3f; // 定义无穷大值
int f[N]; // 辅助数组(未使用)
vector<int> score[N + 2]; // 存储每个类别的分数
vector<int> a, b; // 存储输入的类别和分数
// 自定义排序函数:降序排列
bool cmp(int i, int j)
{
return i > j;
}
int main()
{
int n, m, k;
// 输入m个类别,n个数据,达标线k
cin >> m >> n >> k;
// 调整向量大小
a.resize(n);
b.resize(n);
// 输入类别数据
for (int i = 0; i < n; i++)
cin >> a[i];
// 输入分数数据并分类存储
for (int i = 0; i < n; i++)
{
cin >> b[i];
score[a[i]].emplace_back(b[i]); // 将分数存入对应类别
}
vector<int> need(m + 2); // 存储每个类别需要的最少数据量
int ans = 0; // 总最少需要的数据量
int mx_meed = 0; // 最大需要的数量
int mx_need_i = -1; // 需要最多数据的类别索引
// 计算每个类别需要的数据量
for (int i = 1; i <= m; i++)
{
// 对该类别分数降序排序
sort(score[i].begin(), score[i].end(), cmp);
int sum = 0;
// 计算达到k分需要的最少数据量
for (int j = 0; j < (int)score[i].size(); j++)
{
sum += score[i][j];
if (sum >= k)
{
need[i] = j + 1; // 记录需要的数据量
break;
}
}
// 如果无法达到k分
if (sum < k)
{
puts("-1");
return 0;
}
ans += need[i]; // 累加到总数
// 记录需要最多数据的类别
if (need[i] > mx_meed)
{
mx_meed = need[i];
mx_need_i = i;
}
}
// 如果最大需求类别可以满足条件
if (mx_meed - 1 <= ans - mx_meed)
{
cout << ans << endl;
return 0;
}
// 计算其他类别可以提供的额外数据量
int last = 0;
for (int i = 1; i <= m; i++)
{
if (i != mx_need_i)
{
last += score[i].size() - need[i];
}
}
// 判断是否可以满足最大需求类别的条件
cout << (mx_meed - 1 <= ans - mx_meed + last ? 2 * mx_meed - 1 : -1);
return 0;
}
【运行结果】
3 5 10
1 1 2 3 3
9 1 10 10 1
4
浙公网安备 33010602011771号