搭配购买(dp+并查集优化)

本文探讨了在有限预算下如何最大化购买云朵价值的问题,通过使用动态规划和并查集优化方法解决此问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

Joe觉得云朵很美,决定去山上的商店买一些云朵。商店里有n朵云,云朵被编号为1,2,……,n,并且每朵云都有一个价值。但是商店老板跟他说,一些云朵要搭配来买才好,所以买一朵云则与这朵云有搭配的云都要买。

但是Joe的钱有限,所以他希望买的价值越多越好。

Input

第1行n,m,w,表示n朵云,m个搭配,Joe有w的钱。

第2到n+1行,每行ci,di表示i朵云的价钱和价值。

第n+2到n+1+m行,每行表示ui,vi,表示买ui就必须买vi,同理,如果买vi就必须买ui。

Output

一行,表示可以获得的最大价值。

Sample Input

5 3 10
3 10
3 10
3 10
5 100
10 1
1 3
3 2
4 2

Sample Output

1

Hint

30%的数据保证:n<=100

50%的数据保证:n<=1,000;m<=100;w<=1,000

100%的数据保证:n<=10,000;0<=m<=5000;w<=10,000

 

这道题目是dp+并查集优化。首先题目说买一朵云则与这朵云有搭配的云都要买,那么我们可以把全部有搭配的云都合并起来,变成一件物品。用01背包求出答案。

 F[I,j]=max(f[i-1,j],f[i-1,j-a[i]]+b[i]) (1<=i<=n,1<=j<=w)

 (A为代价,B为价值)

标程:

var

 a:array [1..10000,1..2] of longint;

 p,b,f,c:array [0..10000] of longint;

 n,m,i,j,w,x,y,k:longint;

function find(x:longint):longint;

var y,root,w:longint;

 begin

 y:=x;

   while p[y]>0 do

    y:=p[y];

   root:=y;

   y:=x;

   while p[y]>0 do

    begin

     w:=p[y];

     p[y]:=root;

     y:=w;

    end;

   find:=root;

 end;

 

procedure union(x,y:longint);

 var u,v:longint;

begin

 u:=find(x);

 v:=find(y);

 if u>v then

  begin

   u:=u xor v;

   v:=u xor v;

   u:=u xor v;

  end;

 if u<>v then

  begin

   p[v]:=u;

   a[u,1]:=a[u,1]+a[v,1];

   a[u,2]:=a[u,2]+a[v,2];

  end;

end;

 

function max(x,y:longint):longint;

 begin

  if x>y then exit(x)

         else exit(y);

 end;

 

begin

  readln(n,m,w);

  for i:=1 to n do

   for j:=1 to 2 do

    read(a[i,j]);

 

  for i:=1 to m do

   begin

    read(x,y);

    union(x,y);

   end;

 

 for i:=1 to n do

  begin

   if find(i)=i then

    begin

     inc(k);

     b[k]:=a[i,1];

     c[k]:=a[i,2];

    end;

  end;

 

 for i:=1 to k do

  begin

   for j:=w downto b[i] do

    f[j]:=max(f[j],f[j-b[i]]+c[i]);

  end;

 write(f[w]);

end.

 

 

<think>我们正在讨论C++中的upper_bound函数的使用。根据用户的问题,他想要理解并优化代码片段:`upper_bound(dp+1, dp+res+1, f[i]) - dp` 的作用和用法。 首先,我们来分析这个表达式: - `upper_bound` 是C++标准库中的一个函数,用于在已排序的序列中查找第一个大于给定值的元素的位置。 - 它的基本用法是:`upper_bound(first, last, value)`,其中`first`和`last`是序列的迭代器(或指针),`value`是要查找的值。 - 它返回一个迭代器(或指针),指向序列中第一个大于`value`的元素。如果找不到这样的元素,则返回`last`。 在表达式`upper_bound(dp+1, dp+res+1, f[i]) - dp`中: - `dp` 是一个数组(或指针),`dp+1`指向数组的第二个元素(索引1),`dp+res+1`指向数组索引为`res+1`的位置(即范围是`[1, res]`,因为序列从`dp+1`开始,到`dp+res+1`结束,实际上是左闭右开区间`[dp+1, dp+res+1)`)。 - 因此,`upper_bound(dp+1, dp+res+1, f[i])` 会在数组`dp`的子数组(从索引1到索引`res`)中查找第一个大于`f[i]`的元素的位置。 - 然后,减去`dp`(数组的起始地址)得到的是该元素在数组中的索引(下标)。因为`dp`是数组的起始地址,所以`指针 - dp`得到的就是从0开始的下标。但是注意,这里搜索的范围是从`dp+1`开始,所以返回的下标可能是从1到`res+1`(如果找不到,则返回`dp+res+1`,减去`dp`后得到`res+1`)。 例如,假设`dp`数组从索引0开始,但这里我们使用索引1到`res`(即`dp[1]`到`dp[res]`)。那么,`upper_bound(dp+1, dp+res+1, f[i])`返回的指针可能是`dp+k`(其中`k`在1到`res+1`之间),然后减去`dp`得到`k`(即下标k)。注意,如果返回的是`dp+res+1`,那么减去`dp`就是`res+1`。 这个表达式的作用是:在`dp`数组的[1, res]区间内,找到第一个大于`f[i]`的元素的位置(下标)。如果所有元素都不大于`f[i]`,则返回`res+1`。 这种用法常见于动态规划中维护一个有序的序列,特别是用于优化最长上升子序列(LIS)等问题的O(nlogn)解法。在LIS问题中,我们经常维护一个数组`dp`,其中`dp[i]`表示长度为i的上升子序列的最后一个元素的最小值。然后,对于每个新元素`f[i]`,我们使用`upper_bound`(或`lower_bound`)来查找插入位置,从而更新`dp`数组。 在最长上升子序列问题中,通常我们使用`lower_bound`来查找第一个大于等于当前元素的位置(用于非严格递增)或者严格递增时使用`lower_bound`(因为要求严格递增时,如果存在相等的元素,则不能放在同一个长度上,但实际做法中,我们通常用`lower_bound`来找到替换位置)。而`upper_bound`则用于严格递增序列中,查找第一个大于当前元素的位置(即替换位置)。 然而,在用户给出的代码片段中,使用的是`upper_bound`,所以它查找的是第一个大于`f[i]`的位置。这个位置可以用于替换操作,从而维护一个严格递增的序列。 例如,在最长递增子序列的O(nlogn)算法中: - 我们维护一个数组`dp`,`dp[1...res]`是当前长度为1,2,...,res的递增子序列的末尾元素的最小值,并且这个数组是递增的。 - 对于每个元素`a[i]`: - 如果`a[i]`大于`dp[res]`,则扩展:`dp[++res] = a[i]`。 - 否则,在`dp[1..res]`中找到第一个大于`a[i]`的位置`pos`(即`upper_bound`返回的位置),然后令`dp[pos] = a[i]`。 这里,`pos = upper_bound(dp+1, dp+res+1, a[i]) - dp`,然后更新`dp[pos] = a[i]`。这样,我们保证了`dp`数组的单调性,并且用更小的值替换,使得后续扩展的可能性更大。 因此,用户代码中的表达式就是用来获取这个位置的索引。 关于优化:使用`upper_bound`本身已经是二分查找,时间复杂度为O(logn),所以效率已经很高。但我们可以考虑以下几点优化: 1. 确保数组`dp`是严格递增的,这样`upper_bound`才能正确工作。 2. 如果数组`dp`的大小(即`res`)很大,可以考虑使用更高效的二分查找实现,但标准库的`upper_bound`已经足够高效。 3. 在循环中,如果`f[i]`比当前`dp`数组的最大值(即`dp[res]`)还要大,那么可以直接添加到末尾,避免二分查找。即: ```c++ if (f[i] > dp[res]) { dp[++res] = f[i]; } else { int pos = upper_bound(dp+1, dp+res+1, f[i]) - dp; dp[pos] = f[i]; } ``` 这样可以减少一些不必要的查找(虽然最坏情况下还是O(logn))。 4. 注意数组边界:确保`res`在合理范围内,避免数组越界。 另外,用户提供的引用中,引用[1]和[4]提到了`lower_bound`和`upper_bound`的用法,引用[2]和[5]则展示了一些动态规划的代码,其中引用[2]的代码中使用了`lower_bound`。我们可以参考这些代码来理解。 在引用[2]的代码中,有: ```c++ int now = e[i].x - e[i].len; int num = lower_bound(cf+1, cf+1+n, now) - cf; ``` 这里,`lower_bound`在数组`cf`(从1到n)中查找第一个大于等于`now`的位置,返回该位置的指针,减去`cf`得到下标`num`。如果找不到,则返回`n+1`。 而用户询问的是`upper_bound`,它与`lower_bound`的区别在于: - `lower_bound`:返回第一个大于等于value的元素位置。 - `upper_bound`:返回第一个大于value的元素位置。 因此,在需要严格大于的情况下,使用`upper_bound`。 总结: - 表达式`upper_bound(dp+1, dp+res+1, f[i]) - dp`的作用:在数组`dp`的[1, res]范围内(即从`dp[1]`到`dp[res]`)查找第一个大于`f[i]`的元素的下标(如果找不到,返回`res+1`)。 - 用法:通常用于动态规划中维护单调序列,比如优化最长上升子序列(LIS)问题。 - 优化:可以添加一个判断,如果当前值大于序列最大值则直接扩展,避免二分查找。 最后,根据系统指令,我们需要生成相关问题: §§相关问题§§ 1. 在什么情况下应该使用`upper_bound`而不是`lower_bound`? 2. 如何用`upper_bound`实现最长上升子序列(LIS)的O(nlogn)算法? 3. 在使用`upper_bound`时,如何处理数组下标从0开始的情况? 4. 除了动态规划,`upper_bound`还有哪些常见的应用场景?</think>### `upper_bound(dp+1, dp+res+1, f[i]) - dp` 的作用和用法详解 #### 1. **核心作用** 该表达式在已排序的数组 `dp` 的子区间 `[1, res]` 中,**查找第一个严格大于 `f[i]` 的元素的位置(下标)**。返回值是一个整数下标,范围在 `1` 到 `res+1` 之间: - 若找到满足条件的元素,返回其下标(如 `k`)。 - 若所有元素均不大于 `f[i]`,则返回 `res+1`(表示越界位置)。 #### 2. **关键语法解析** - **`upper_bound(first, last, value)`** - `first`:搜索区间的起始指针(`dp+1` 指向 `dp[1]`)。 - `last`:搜索区间的结束指针(`dp+res+1` 指向 `dp[res]` 的下一个位置,即左闭右开区间 `[dp+1, dp+res+1)`)。 - `value`:待比较的值(`f[i]`)。 - **`- dp` 的意义** 将返回的指针转换为下标(指针差值)。若返回指针为 `dp+k`,则 `(dp+k) - dp = k`(即下标 `k`)。 #### 3. **典型应用场景** 常见于动态规划(DP)中维护**单调递增序列**,例如优化最长上升子序列(LIS)问题: ```cpp int res = 0; // 当前序列长度 for (int i = 1; i <= n; i++) { // 在 dp[1..res] 中找第一个大于 f[i] 的位置 int pos = upper_bound(dp+1, dp+res+1, f[i]) - dp; if (pos > res) { dp[++res] = f[i]; // 扩展序列 } else { dp[pos] = f[i]; // 替换为更小的值,保持递增性 } } ``` - **优化效果**:将 LIS 的复杂度从 $O(n^2)$ 降至 $O(n \log n)$[^1][^5]。 #### 4. **与 `lower_bound` 的区别** | 函数 | 返回值条件 | 示例(数组 `[1,3,5]`, `value=3`) | |---------------|--------------------------|-----------------------------------| | `lower_bound` | 第一个 **≥** `value` 的元素 | 返回下标 `2`(元素 `3`) | | `upper_bound` | 第一个 **>** `value` 的元素 | 返回下标 `3`(元素 `5`) | #### 5. **优化建议** 1. **提前判断边界**:若 `f[i]` 大于当前最大值 `dp[res]`,直接扩展序列,避免二分查找: ```cpp if (f[i] > dp[res]) { dp[++res] = f[i]; } else { int pos = upper_bound(dp+1, dp+res+1, f[i]) - dp; dp[pos] = f[i]; } ``` 2. **确保数组有序**:`dp[1..res]` 必须严格递增,否则结果错误。 3. **下标从 0 开始的处理**:若数组下标从 `0` 开始,需调整指针范围: ```cpp // 在 dp[0..res-1] 中查找 int pos = upper_bound(dp, dp+res, f[i]) - dp; ``` #### 6. **示例分析** 假设 `dp = [0, 2, 5, 7]`(有效区间 `[1,3]`),`res=3`: - 若 `f[i] = 4`:`upper_bound` 返回 `dp+3`(元素 `5`),下标 `pos = 3`。 - 若 `f[i] = 9`:返回 `dp+4`(越界),`pos = 4`(需扩展序列)。 --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值