CF1851E. Nastya and Potions 题解 DAG上的DP
题目链接:https://codeforces.com/problemset/problem/1851/E
视频讲解地址:https://www.bilibili.com/video/BV1tZ1FYPELp/
解题思路:
首先,可以做一下预处理,因为编号为 \(p_i\) 的药水可以免费无限供应,所以可以直接令所有的 \(c_{p_i} = 0\)。
其次,这是一个 DAG。
所以可以把建一个有向图:如果混合得到第 \(u\) 瓶药水需要一瓶第 \(v\) 瓶药水,则在有向图中连一条 \(v \rightarrow u\) 的有向边。
然后你就可以发现,对于第 \(u\) 瓶药水,只有两个选择:
- 方案一:单独购买第 \(u\) 瓶药水,花费 \(c_u\) 个金币。
- 方案二:混合得到第 \(u\) 瓶药水,花费的是所有需要的材料的最少花费之和。
即:
定义 \(f_u\) 为得到一瓶第 \(u\) 瓶药水的最小花费,则方案二可以表示为 \(\sum\limits_{v \rightarrow u} f_v\),这里 \(v\) 对应的是所有直接连向 \(u\) 的节点。
同时,入度为 \(0\) 的点只能选择方案一。
我们用 \(d_u\) 表示节点 \(u\) 的入度。
则:
- 若 \(d_u = 0\),则 \(f_u = c_u\) ;
- 若 \(d_u \gt 0\),则 \(f_u = \min( c_u \ , \sum\limits_{v \rightarrow u} f_v )\) 。
基于上述思路,我们可以用两种方式解决这个问题。
- 方式一:拓扑排序;
- 方式二:建反图 + 记忆化搜索。
示例程序1(拓扑排序):
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5;
int T, n, k, c[maxn], d[maxn];
vector<int> g[maxn];
long long f[maxn];
void init() {
for (int i = 1; i <= n; i++) {
g[i].clear();
f[i] = 0;
}
}
void tp_sort() {
queue<int> que;
for (int i = 1; i <= n; i++)
if (!d[i])
f[i] = c[i], que.push(i);
while (!que.empty()) {
int u = que.front();
que.pop();
for (auto v : g[u]) {
f[v] += f[u];
if (--d[v] == 0)
f[v] = min(f[v], 1ll * c[v]),
que.push(v);
}
}
}
int main() {
scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &k);
init();
for (int i = 1; i <= n; i++)
scanf("%d", c+i);
for (int i = 0; i < k; i++) {
int p;
scanf("%d", &p);
c[p] = 0;
}
for (int u = 1; u <= n; u++) {
int v;
scanf("%d", d+u); // d[u]
for (int i = 0; i < d[u]; i++) {
scanf("%d", &v);
g[v].push_back(u);
}
}
tp_sort(); // 拓扑排序
for (int i = 1; i <= n; i++)
printf("%lld ", f[i]);
puts("");
}
return 0;
}
示例程序2(建反图 + 记忆化搜索):
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5;
int T, n, k, c[maxn], d[maxn];
vector<int> g[maxn];
bool vis[maxn];
long long f[maxn];
void init() {
for (int i = 1; i <= n; i++) {
g[i].clear();
vis[i] = false;
}
}
long long dfs(int u) { // 返回 f[u]
if (vis[u])
return f[u];
vis[u] = true;
f[u] = c[u];
if (!d[u]) return f[u];
long long tmp = 0;
for (auto v : g[u])
tmp += dfs(v);
return f[u] = min(f[u], tmp);
}
int main() {
scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &k);
init();
for (int i = 1; i <= n; i++)
scanf("%d", c+i);
for (int i = 0; i < k; i++) {
int p;
scanf("%d", &p);
c[p] = 0;
}
for (int u = 1; u <= n; u++) {
int v;
scanf("%d", d+u); // d[u]
for (int i = 0; i < d[u]; i++) {
scanf("%d", &v);
g[u].push_back(v);
}
}
for (int i = 1; i <= n; i++)
printf("%lld ", dfs(i));
puts("");
}
return 0;
}