H 制造游戏币
https://ac.nowcoder.com/acm/contest/22842/H]
我们首先根据每个限制的 b b b 和 c c c 都不相同可以得到,他们的关系一定是一条链。那么我们可以对链做一些预处理即可解决这个问题。一条链上要求拿的物品个数要递增,那么,我们首先把至少要拿的拿掉,也就是分别拿 0 , 1 , 2 , 3... 0,1,2,3... 0,1,2,3... 然后,我们可以这么想,拿一个 x x x 就至少要拿一个 x x x 的整个后缀,因为这样才能至少保持递增,那么我们可以将 x x x 的后缀当成一个物品去拿,我们发现这样随意拿 x x x 的后缀的所有情况唯一映射了一个保持整个序列递增的情况。那么我们现在要求保证链上递增的情况,就等价于求另所有链上的 x x x 的权值变为 x x x 的后缀和,然后随意拿任一物品任一数量,容量为 T T T 的情况,也就是变成完全背包了。
2021.11.17更新:感谢我改名了oo的指正,这里的 T T T 减去必拿的物品之后是可能成负数的,最后如果直接输出 d p [ T ] dp[T] dp[T] 是会越界的,需要特判一下。还有这里的链是有可能成环的,那么此时应该是一个非法情况,无论背包容量是多少都无法拿取,所以最终结果是 0 0 0 ,所以代码中应该还要判一个环。
#include <bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 7;
int n, m, T, a[303], dp[100010], f[100010];
int in[303], vis[303], nxt[303];
bool ring = false;
int ff(int x) {
return f[x] == x ? x : f[x] = ff(f[x]);
}
void dfs(int u, int k) {
T -= k * a[u];
if (nxt[u]) {dfs(nxt[u], k + 1); a[u] += a[nxt[u]];}
}
int main() {
scanf("%d%d%d", &n, &m, &T);
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
f[i] = i;
}
for (int i = 0; i < m; ++i) {
int u, v;
scanf("%d%d", &v, &u);
int fu = ff(u), fv = ff(v);
if (fu == fv) {
ring = true;
}
else {
f[fu] = fv;
}
nxt[u] = v;
vis[u] = vis[v] = 1;
in[v]++;
}
if (ring) {
puts("0");
return 0;
}
for (int i = 1; i <= n; ++i) {
if (vis[i] && !in[i]) {
dfs(i, 0);
}
}
dp[0] = 1;
for (int i = 1; i <= n; ++i) {
for (int j = a[i]; j <= T; ++j) {
dp[j] = (dp[j] + dp[j - a[i]]) % mod;
}
}
if (T >= 0) {
printf("%d", dp[T]);
}
else {
puts("0");
}
}