寿司餐厅[SHOI2017]
题目描述
Kiana 最近喜欢到一家非常美味的寿司餐厅用餐。
每天晚上,这家餐厅都会按顺序提供 \(n\) 种寿司,第 \(i\) 种寿司有一个代号 \(a_i\) 和美味度 \(d_{i, i}\),不同种类的寿司有可能使用相同的代号。每种寿司的份数都是无限的,Kiana 也可以无限次取寿司来吃,但每种寿司每次只能取一份,且每次取走的寿司必须是按餐厅提供寿司的顺序连续的一段,即 Kiana 可以一次取走第 \(1, 2\) 种寿司各一份,也可以一次取走第 \(2, 3\) 种寿司各一份,但不可以一次取走第 \(1, 3\) 种寿司。
由于餐厅提供的寿司种类繁多,而不同种类的寿司之间相互会有影响:三文鱼寿司和鱿鱼寿司一起吃或许会很棒,但和水果寿司一起吃就可能会肚子痛。因此,Kiana 定义了一个综合美味度 \(d_{i, j} \ (i < j)\),表示在一次取的寿司中,如果包含了餐厅提供的从第 \(i\) 份到第 \(j\) 份的所有寿司,吃掉这次取的所有寿司后将获得的额外美味度。由于取寿司需要花费一些时间,所以我们认为分两次取来的寿司之间相互不会影响。注意在吃一次取的寿司时,不止一个综合美味度会被累加,比如若 Kiana 一次取走了第 \(1, 2, 3\) 种寿司各一份,除了 \(d_{1, 3}\) 以外,\(d_{1, 2}, d_{2, 3}\)也会被累加进总美味度中。
神奇的是,Kiana 的美食评判标准是有记忆性的,无论是单种寿司的美味度,还是多种寿司组合起来的综合美味度,在计入 Kiana 的总美味度时都只会被累加一次。比如,若 Kiana 某一次取走了第 \(1, 2\) 种寿司各一份,另一次取走了第 \(2, 3\) 种寿司各一份,那么这两次取寿司的总美味度为 \(d_{1, 1} + d_{2, 2} + d_{3, 3} + d_{1, 2} + d_{2, 3}\),其中 \(d_{2, 2}\) 只会计算一次。
奇怪的是,这家寿司餐厅的收费标准很不同寻常。具体来说,如果 Kiana 一共吃过了 \(c \ (c > 0)\) 种代号为 \(x\) 的寿司,则她需要为这些寿司付出 \(mx^2 + cx\),其中 \(m\) 是餐厅给出的一个常数。
现在 Kiana 想知道,在这家餐厅吃寿司,自己能获得的总美味度(包括所有吃掉的单种寿司的美味度和所有被累加的综合美味度)减去花费的总钱数的最大值是多少。由于她不会算,所以希望由你告诉她。
题面又臭又长
输入格式
第一行包含两个正整数 \(n, m\),分别表示这家餐厅提供的寿司总数和计算寿司价格中使用的常数。
第二行包含 \(n\) 个正整数,其中第 \(k\) 个数 \(a_k\) 表示第 \(k\) 份寿司的代号。
接下来 \(n\) 行,第 \(i\) 行包含 \(n - i + 1\) 个整数,其中第 \(j\) 个数 \(d_{i, i+j-1}\) 表示吃掉寿司能获得的相应的美味度,具体含义见问题描述。
输出格式
输出共一行包含一个正整数,表示 Kiana 能获得的总美味度减去花费的总钱数的最大值。
题解
注意一下 吃寿司的代价和你吃了多少种有关,而不是吃了多少个。。。
第一眼看是DP 然而很快就发现有时候取重叠的区间可能更优 于是DP就没有了
而且数据范围这么小 怎么看都是网络流 但是蒟蒻不会建图
回头看了一下题解 发现其实还挺套路的。。。
一个经典的最小割模型(好像还叫什么最大权闭合子图?)
我们对于每个寿司区间都建立一个点 如果最小割割完后这个点在源点的联通块就表示我们选了这个区间 如果在汇点的联通块就表示没选
- 对于有着正美味度的区间\([i,j]\),从源点向它连一条流量为美味度\(d_{i,j}\)的边,表示不选这个点要付出\(d_{i,j}\)的代价
- 对于有着负美味度的区间\([i,j]\),从它向汇点连一条流量为\(-d_{i,j}\)的边,表示选了这个点要付出\(-d_{i,j}\)的代价
因为如果选了\([i,j]\),必然选了它的所有子区间,所以我们对于每个区间\([i,j]\):
- 从\([i,j]\)向\([i+1,j]\)和\([i,j+1]\)分别连一条流量为\(inf\)的边,表示选了\([i,j]\)必须选剩下两个
然后看看每个寿司的选择代价要怎么处理
我们同样对于每个单个的寿司建一个点
- 对于每个寿司点\(i\),向汇点连一条流量为代号\(a_i\)的边 表示选过这个寿司要付出\(a_i\)的代价
- 从区间点\([i,i]\)向寿司点\(i\)连一条流量为\(inf\)的边 表示选了前者必须选后者
最后看看那个\(mx^2\)的代价怎么处理:
如法炮制,我们对于每个寿司代号建一个点
- 对于代号点\(x\),向汇点连一条流量为\(mx^2\)的边
- 从每个代号为\(x\)的寿司点 向这个代号点\(x\)连一条\(inf\) 表示选了那个寿司就要付出代号的\(mx^2\)代价
最小割等于最大流 用dinic求出
然后我们注意到类型1的边是"不选它会付出\(d_{i,j}\)"的代价 所以初始时我们把答案设置成所有正的美味度之和
然后最终答案就是 所有正的美味度之和 减去最小割
代码
#include <bits/stdc++.h>
using namespace std;
template<typename T>
inline void read(T &num) {
T x = 0, _f = 1; char ch = getchar();
for (; ch > '9' || ch < '0'; ch = getchar()) if (ch == '-') _f = -1;
for (; ch <= '9' && ch >= '0'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ '0');
num = x * _f;
}
const int inf = 0x7fffffff;
int n, m, s, t, c[205], val[205][205], d[12005], ans;
int now[12005], head[12005], pre[500005], to[500005], flow[500005], sz = 1;
int ind[205][205], indtp[1005], tot;
bool vis[1005];
inline void addedge(int u, int v, int w) {
pre[++sz] = head[u]; head[u] = sz; to[sz] = v; flow[sz] = w;
}
bool bfs() {
memset(d, 0, sizeof(d));
queue<int> q;
q.push(s); d[s] = 1;
while (!q.empty()) {
int x = q.front(); q.pop();
for (int i = head[x]; i; i = pre[i]) {
int y = to[i];
if (flow[i] > 0 && d[y] == 0) {
d[y] = d[x] + 1;
q.push(y);
}
}
}
return (d[t] != 0);
}
int dfs(int x, int cur) {
if (x == t || cur <= 0) return cur;
int rest = cur;
for (int i = now[x]; i; i = pre[i]) {
now[x] = i; int y = to[i];
if (flow[i] > 0 && d[y] == d[x] + 1) {
int tmp = dfs(y, min(rest, flow[i]));
if (tmp <= 0) d[y] = 0;
flow[i] -= tmp; flow[i^1] += tmp;
rest -= tmp;
if (rest <= 0) break;
}
}
return cur - rest;
}
int dinic() {
int ret = 0;
while (bfs()) {
memcpy(now, head, sizeof(now));
ret += dfs(s, inf);
}
return ret;
}
void preind() {
s = 0;
for (int i = 1; i <= n; i++) {
if (!vis[c[i]]) {
indtp[c[i]] = ++tot;
vis[c[i]] = 1;
}
}
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
ind[i][j] = ++tot;
}
}
t = ++tot;
}
int main() {
read(n); read(m);
tot = n;
for (int i = 1; i <= n; i++) {
read(c[i]);
}
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
read(val[i][j]);
}
}
preind();
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
if (val[i][j] >= 0) {
addedge(s, ind[i][j], val[i][j]); //边类型1
addedge(ind[i][j], s, 0);
ans += val[i][j];
} else {
addedge(ind[i][j], t, -val[i][j]); //边类型2
addedge(t, ind[i][j], 0);
}
if (i != j) {
addedge(ind[i][j], ind[i][j-1], inf); //边类型3
addedge(ind[i][j-1], ind[i][j], 0);
addedge(ind[i][j], ind[i+1][j], inf);
addedge(ind[i+1][j], ind[i][j], 0);
}
}
}
for (int i = 1; i <= n; i++) {
addedge(ind[i][i], i, inf); addedge(i, ind[i][i], 0); //边类型5
addedge(i, t, c[i]); addedge(t, i, 0); //边类型4
addedge(i, indtp[c[i]], inf); addedge(indtp[c[i]], i, 0); //边类型7
}
for (int i = 1; i <= 1000; i++) {
if (vis[i] && m) {
addedge(indtp[i], t, m * i * i); addedge(t, indtp[i], 0); //边类型6
}
}
printf("%d\n", ans - dinic());
return 0;
}