Luogu2792 [JSOI2008]小店购物

Luogu2792 [JSOI2008]小店购物

重题 bzoj4349 最小树形图

\(n\) 个物品,每个物品有价格 \(c_i\) 和所需个数 \(k_i\) ,所有物品必须恰好买 \(k_i\) 个。有 \(m\) 种优惠方案给出 \(x,\ y,\ w\) :若买过至少一件 \(x\) 物品,则 \(y\) 物品只需 \(w\) 的价格 \((w<c_y)\) ,数据中所有 \((x,\ y)\) 不同且 \(x\neq y\) 。求最少花费。所有数据保留两位小数。

\(1\leq n\leq50,\ 0<c_i\leq1000,\ 0\leq k\leq100\)

最小树形图


首先判掉无效的 \(k_i=0\) 的物品,发现如果每种物品都买过一遍,那么随后所有的商品都可以以最低价格购买。那么如何求每种物品各买一个的最小花费呢?可以将 \(m\) 种优惠方案看做树边,再加一个虚拟节点 \(n+1\) ,连边 \((n+1,\ i,\ c_i)\) ,接着跑最小树形图就可以解决了

因为 \(m\)\(n^2\)级别的,所以时间复杂度 \(O(n^3)\)

代码

#include <bits/stdc++.h>
using namespace std;

typedef double db;
const int maxn = 60;
int n, m, mp[maxn], sum[maxn], pre[maxn], vis[maxn], tid[maxn]; db ans, a[maxn], val[maxn];
struct edges {
  int u, v; db w;
} e[maxn * maxn];

db edmonds() {
  int rt = n;
  while (1) {
    memset(tid, 0, sizeof tid);
    memset(vis, 0, sizeof vis);
    for (int i = 1; i <= n; i++) {
      val[i] = 1e9;
    }
    for (int i = 1; i <= m; i++) {
      int u = e[i].u, v = e[i].v;
      if (u != v && e[i].w < val[v]) {
        val[v] = e[i].w, pre[v] = u;
      }
    }
    int tot = 0;
    for (int i = 1; i < n; i++) {
      int u = i;
      ans += val[i];
      while (vis[u] != i && !tid[u] && u != rt) {
        vis[u] = i, u = pre[u];
      }
      if (!tid[u] && u != rt) {
        tid[u] = ++tot;
        for (int v = pre[u]; u != v; v = pre[v]) {
          tid[v] = tot;
        }
      }
    }
    if (!tot) break;
    for (int i = 1; i <= n; i++) {
      if (!tid[i]) tid[i] = ++tot;
    }
    for (int i = 1; i <= m; i++) {
      int u = e[i].u, v = e[i].v;
      e[i].u = tid[u], e[i].v = tid[v];
      if (u != v) e[i].w -= val[v];
    }
    rt = tid[rt], n = tot;
  }
  return ans;
}

int main() {
  int t1, t2;
  scanf("%d", &t1);
  for (int i = 1; i <= t1; i++) {
    db x; int y;
    scanf("%lf %d", &x, &y);
    if (y) mp[i] = ++n, a[n] = x, sum[n] = y;
  }
  scanf("%d", &t2);
  m = n + t2, n++;
  for (int i = 1; i < n; i++) {
    e[t2 + i].u = n, e[t2 + i].v = i, e[t2 + i].w = a[i];
  }
  for (int i = 1; i <= t2; i++) {
    int u, v; db w;
    scanf("%d %d %lf", &u, &v, &w);
    u = mp[u], v = mp[v];
    if (!u || !v) continue;
    a[v] = min(a[v], w);
    e[i].u = u, e[i].v = v, e[i].w = w;
  }
  for (int i = 1; i <= n; i++) {
    ans += a[i] * (sum[i] ? sum[i] - 1 : 0);
  }
  printf("%.2f", edmonds());
  return 0;
}

转载于:https://www.cnblogs.com/Juanzhang/p/10380717.html

根据引用[1],dp[u][j]表示在u子树中选取恰好j个人时能获得的最大价值。而根据引用,该问题的时间复杂度为O(log2​104×nm)。 对于洛谷P2143 [JSOI2010] 巨额奖金问题,我们可以使用动态规划来解决。具体步骤如下: 1. 首先,我们需要构建一棵树来表示员工之间的关系。树的根节点表示公司的总经理,其他节点表示员工。每个节点都有一个权值,表示该员工的奖金金额。 2. 接下来,我们可以使用动态规划来计算每个节点的dp值。对于每个节点u,我们可以考虑两种情况: - 如果选择节点u,则dp[u][j] = dp[v][j-1] + value[u],其中v是u的子节点,value[u]表示节点u的奖金金额。 - 如果不选择节点u,则dp[u][j] = max(dp[v][j]),其中v是u的子节点。 3. 最后,我们可以通过遍历树的所有节点,计算出dp[u][j]的最大值,即为所求的巨额奖金。 下面是一个示例代码,演示了如何使用动态规划来解决洛谷P2143 [JSOI2010] 巨额奖金问题: ```python # 构建树的数据结构 class Node: def __init__(self, value): self.value = value self.children = [] # 动态规划求解最大奖金 def max_bonus(root, j): dp = [[0] * (j+1) for _ in range(len(root)+1)] def dfs(node): if not node: return for child in node.children: dfs(child) for k in range(j, 0, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-1] + node.value) for child in node.children: for k in range(j, 0, -1): for l in range(k-1, -1, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-l-1] + dp[child.value][l]) dfs(root) return dp[root.value][j] # 构建树 root = Node(1) root.children.append(Node(2)) root.children.append(Node(3)) root.children[0].children.append(Node(4)) root.children[0].children.append(Node(5)) root.children[1].children.append(Node(6)) # 求解最大奖金 j = 3 max_bonus_value = max_bonus(root, j) print("最大奖金为:", max_bonus_value) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值