不辣菜摆在某个位置的收益只由面前三位教授决定,于是贪心选收益最大的 n-a 个位置即可。
OJ: luogu
题目 ID: P9728
难度:普及
标签:贪心思维贡献法排序构造
创建: 2026-10-02 15:23
更新: 2026-10-07 12:15
形式化题目
有一个长度为
位置 p-1
|
教授 p-1 | 教授 p | 教授 p+1
|
位置 p+1
教授 i 能访问:位置 i-1、位置 i、位置 i+1(下标模 n)给出
- 若
:满意度为 (能拿到的菜有多少就算多少)。 - 若
:满意度为三个可达位置中不辣菜的盘数。
求所有教授满意度之和的最大值。
暴力解法
思路
最直白的做法是真正去「摆菜」:由于菜只是辣与不辣两种,摆法完全由「哪些位置放不辣菜」决定。枚举所有大小为
这个做法不需要任何观察,只要把题面翻成代码即可,因此它天然是正确的,适合用来对拍。
代码
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-02 15:23
* update_at: 2026-10-03 13:15
*
* P9728 [EC Final 2022] Dining Professors
* 暴力解:辣菜之间没有区别、不辣菜之间也没有区别,
* 于是只需枚举「哪 n-a 个位置放不辣菜」这 C(n, n-a) 种情况,
* 对每种情况老老实实按定义统计满意度,取最大值。
* 只适合 n<=20 左右的小数据,用于对拍。
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
int n; // 教授人数 = 位置数
int a; // 辣菜数量
int b[maxn]; // b[i]=1:教授 i 能吃辣
int pos[maxn]; // pos[p]=1:位置 p 放的是不辣菜
ll best; // 当前找到的最大满意度
// dep: 正在决定第 dep 个位置
// spicyCnt: 已经放掉的辣菜数量
void dfs(int dep, int spicyCnt) {
// 剩下的位置全放辣菜也不够 n-a 道不辣菜,剪枝
if (spicyCnt > a) {
return;
}
if (dep == n) {
if (spicyCnt != a) {
return;
}
ll tot = 0;
for (int i = 0; i < n; ++i) {
int l = (i - 1 + n) % n; // 左边位置
int r = (i + 1) % n; // 右边位置
if (b[i] == 1) {
tot += 3; // 能吃辣:三盘菜都算满意
} else {
tot += pos[l] + pos[i] + pos[r]; // 不能吃辣:只数不辣菜
}
}
if (tot > best) {
best = tot;
}
return;
}
// 位置 dep 放辣菜
pos[dep] = 0;
dfs(dep + 1, spicyCnt + 1);
// 位置 dep 放不辣菜
pos[dep] = 1;
dfs(dep + 1, spicyCnt);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> a;
for (int i = 0; i < n; ++i) {
cin >> b[i];
}
best = -1;
dfs(0, 0);
cout << best << "\n";
return 0;
}复杂度
枚举的子集数为
瓶颈
暴力真正的浪费在于「把不辣菜当成了有区别的
另一种朴素的暴力是枚举所有
种「菜的全排列」,那是把同色菜也当成不同的,正确但更慢,这里不采用。
正解
思路
第一步:能吃辣的教授贡献固定。
如果
这部分与摆法无关,先记作底数,之后只需关心
第二步:把「不辣菜放在哪里」写成可加的收益。
记
对一位
这里
第三步:收益彼此独立,贪心选最大的
关键点在于:
具体实现就是把
样例走一遍。
| 位置 |
三位相关教授 |
其中 |
|---|---|---|
| 0 | 1 | |
| 1 | 1 | |
| 2 | 2 | |
| 3 | 1 | |
| 4 | 1 |
排序后前
代码
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-02 15:23
* update_at: 2026-10-03 13:15
*
* P9728 [EC Final 2022] Dining Professors
* 正解:每个位置放一道不辣菜,对总满意度的增益 = (1-b[i-1])+(1-b[i])+(1-b[i+1]);
* 辣菜放到任何位置都不贡献,因此把 n-a 道不辣菜放到增益最大的 n-a 个位置。
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1e5 + 5;
int n; // 教授人数 = 位置数
int a; // 辣菜数量
int b[maxn]; // b[i]=1:教授 i 能吃辣; b[i]=0:教授 i 不能吃辣
int c[maxn]; // c[p]:在位置 p 放一道不辣菜带来的满意度增益
void read_data() {
cin >> n >> a;
for (int i = 0; i < n; ++i) {
cin >> b[i];
}
}
signed main () {
ios::sync_with_stdio(false); cin.tie(0);
read_data();
// 能吃辣的教授:无论摆什么菜都是 3 道菜可吃,先把这部分作为底数
ll ans = 0;
for (int i = 0; i < n; ++i) {
if (b[i] == 1) {
ans += 3;
}
}
// 对不能吃辣的教授,只有不辣菜才算满意
// 位置 p 摆在教授 p-1、p、p+1 面前,所以一道不辣菜影响这三位教授
for (int p = 0; p < n; ++p) {
int l = (p - 1 + n) % n; // 左边教授
int r = (p + 1) % n; // 右边教授
c[p] = (1 - b[l]) + (1 - b[p]) + (1 - b[r]);
}
// 一共要选 n-a 个位置放不辣菜,贪心选增益最大的那些
sort(c, c + n, greater<int>());
for (int k = 0; k < n - a; ++k) {
ans += c[k];
}
cout << ans << "\n";
return 0;
}复杂度
计算
总结
- 先抽出与摆法无关的常量:能吃辣的教授恒贡献
。 - 再用贡献法换视角:不辣菜放在位置
的收益是固定值 ,与其它位置无关。 - 约束退化成「恰好选
个位置」后,线性目标 + 个数限制 ⇒ 排序取最大的 个即可,无需 DP 或匹配。
这道题最容易卡住的地方是看到「环形摆菜」就去做复杂的状态或环形 DP;一旦发现每道不辣菜的收益可以单独计算、彼此不冲突,问题就坍缩成一次排序。