P6775 NOI2020 制作菜品
给定正整数
有一个
- 一行最多出现两种颜色。
- 第
种颜色必须恰好被使用 次。
多测,最多
虽然这题是个黑题,但是我们仍然可以发现,
- 先对
排序。 - 对
建立权值数组,在权值数组上做。
这里一看就是第一种,那就先对
然后我们开始观察这个仅次于暴力的第一档部分分。
首先我们发现,如果没有每行颜色种类限制,只要按照
因此,对于要求使用次数最少的颜色,我们可以让它和次数最多的颜色一起涂,这样贪心还是比较优秀的。
简单观察不难发现(这里的
。- 否则
显然不成立。
- 否则
( 时)。- 反证法。假设
,则 ,则 ,和 矛盾。
- 反证法。假设
注意到上面两条的证明依赖于
综上所述,我们可以在第一行直接涂上
这样以来,对于第
归纳的边界是
(此时剩下的那个颜色
其实整道题不难发现,在
事实上确实如此,直接把颜色数补齐到
到这里已经解决 45 分了。
我们观察一下新建颜色的实质,其实就是让前
所以代码实现就不用新建颜色了,让头
到这里没啥思路了,不妨考虑构造最常用的方法:建图。尤其是每行最多涂两个颜色的限制,启发我们对每行所涂的两种颜色连边。
那么题目变成:对
这里一行颜色全为
看起来无从下手,但是其实
- 对于前
条边,我们连接点权最大的点 和任意点 ,并将 的点权分配 的权值给这条边。(这里分配完权值后, 的点权也要动态地减去 )。 - 对于后
条边,我们连接点权最大的点 和点权最小的 未标记点 ,然后:- 将
的点权分配 给这条边。 - 将
的点权 全部分配给这条边,并 标记 。
- 将
这里一个点被标记,等价于之前对
那么对于
, 。- 根据连通块的连通性,
。 - 第
个连通块内部的点权明显要被这 条边分配完(因为其它边不分配这些点权),所以 。 - 至少存在两个
满足 。- 否则,若最多存在一个
满足 ,会得到 ,也即 ,矛盾。
- 否则,若最多存在一个
因此,
下面给出找到这样一个
因为
所以存在一个
这个问题很类似背包恰满问题,也就是在
恰满的容量和物品选择的数量有关,不太好处理。可以对
也就是
根据
令
转移是
可以考虑用 bitset 优化,设
f[i] = f[i - 1] | (f[i - 1] << v[i])
。滚动一下得到 f |= f << v[i]
。当然后面要输出方案所以别滚动了。
输出方案:设
背包复杂度是物品数量
然后转化为
所以总复杂度
/*
* @Author: crab-in-the-northeast
* @Date: 2023-07-13 10:02:06
* @Last Modified by: crab-in-the-northeast
* @Last Modified time: 2023-07-13 11:10:23
*/
#include <bits/stdc++.h>
inline int read() {
int x = 0;
bool f = true;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-')
f = false;
for (; isdigit(ch); ch = getchar())
x = (x << 1) + (x << 3) + (ch ^ '0');
return f ? x : (~(x - 1));
}
const int N = 505;
struct node {
int val, id;
bool operator < (node b) {
if (val != b.val)
return val < b.val;
return id < b.id;
}
};
int n, m, k;
std :: bitset <500 * 5000 * 2> f[N];
inline void easy(std :: vector <node> a) {
int n = (int)a.size();
for (int i = 1; i < n; ++i) {
int x = std :: min_element(a.begin(), a.end()) - a.begin();
int y = std :: max_element(a.begin(), a.end()) - a.begin();
int p = a[x].val, q = k - p;
if (p)
printf("%d %d %d %d\n", a[x].id, p, a[y].id, q);
else
printf("%d %d\n", a[y].id, k);
a[y].val -= q;
std :: swap(a[x], a.back());
a.pop_back();
}
}
inline void solve() {
n = read(); m = read(); k = read();
std :: vector <node> a;
for (int i = 1; i <= n; ++i)
a.push_back({read(), i});
if (m >= n - 1) {
for (int i = 1; i <= m - n + 1; ++i) {
int x = std :: max_element(a.begin(), a.end()) - a.begin();
a[x].val -= k;
printf("%d %d\n", a[x].id, k);
}
easy(a);
} else {
f[0].reset();
f[0].set(n * k);
for (int i = 1; i <= n; ++i) {
int v = a[i - 1].val - k;
if (v > 0)
f[i] = (f[i - 1] | (f[i - 1] << v));
else
f[i] = (f[i - 1] | (f[i - 1] >> (-v)));
}
if (!f[n][-k + n * k])
return void(puts("-1"));
std :: vector <node> S, T;
for (int i = n, j = -k + n * k; i; --i) {
int v = a[i - 1].val - k;
if (f[i - 1][j])
T.push_back(a[i - 1]);
else {
S.push_back(a[i - 1]);
j -= v;
}
}
easy(S); easy(T);
}
return ;
}
int main() {
int T = read();
while (T--)
solve();
return 0;
}
如果您是从洛谷题解过来的,觉得这篇题解解决了您的疑惑,帮到了您,别忘了回到洛谷题解区给我题解点个赞!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效