2023-05-05 背包问题

news2024/11/17 16:40:53

背包问题

1 01背包和完全背包问题

01背包问题

有N件物品和一个容量为V的背包,第i件物品的体积是v[i]、价值是w[i],每种物品只可以使用一次,求将哪些物品放入背包可以使得价值总和最大。这里的w是weight即权重的意思

这是最基础的背包问题,"01"就是指每种物品要么选要么不选,我们定义状态 f [ i ] [ j ] f[i][j] f[i][j]表示从前i件物品中选出容量为j的背包能获得的最大价值

状态定义,根据第i个物品选还是不选,分成两种情况

  • 选第i个物品, f [ i ] [ j ] = f [ i − 1 ] [ j − v [ i ] ] + w [ i ] f[i][j] = f[i - 1][j - v[i]] + w[i] f[i][j]=f[i1][jv[i]]+w[i],即从剩余的i-1个物品中选取容量总和为j-v[i]的物品,其价值加上第i个物品的价值w[i]即为最大价值
  • 不选第i个物品, f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i][j] = f[i - 1][j] f[i][j]=f[i1][j],即从剩余的i-1个物品中选取容量总和为j的物品,其价值即为最大价值

取两者的较大值即为最终的最大价值 f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j − v [ i ] ] + w [ i ] , f [ i − 1 ] [ j ] ) f[i][j] = max(f[i - 1][j - v[i]] + w[i], f[i - 1][j]) f[i][j]=max(f[i1][jv[i]]+w[i],f[i1][j])

上面求f[i][j]的时空复杂度都是 O ( N V ) O(NV) O(NV),这是优点高的,如何优化呢?

注意:下面说的"上一轮"和"下一轮"是指i循环中的上一个i值和下一个i值

注意到每次我们都是从 f [ i − 1 ] f[i - 1] f[i1]递推到 f [ i ] f[i] f[i],可以只用O(V)的空间存下一步的f吗?即下一轮的i覆盖上一轮的i,i这一维度只保留长度为1即可,进一步可以直接把第一层去掉,只用j这个第二维度,伪代码如下:

for(int i = 1; i <= N; i++) {
    for(int j = V; j >= v[i]; j--) { // 这里的j为什么是从大到小呢?下面会讲解地
        f[j] = Math.max(f[j], f[j - v[i]] + w[i]);
    }
}

这里的V为什么是从大到小呢?因为这样每次计算f[j]的时候,f[V…j+1]已经是新的一轮的值了,而 f [ j ] f[j] f[j] f [ j − v [ i ] ] f[j - v[i]] f[jv[i]](分别压缩前的f[i - 1][j]f[i - 1][j - v[i]])肯定还没被更新,即为上一轮的值,用上一轮的值更新本轮的值,这样才符合动态规划的套路.

如果V从小到大,那么 f [ j ] f[j] f[j] f [ j − v [ i ] ] f[j - v[i]] f[jv[i]]就会在计算f[j]之前被更新了,这样会导致不能用上一轮的值更新本轮的值,显然不符合动态规划的套路。

简单说就是:由于数组变成一维数组了,所以才必须倒着来,因为后面的数据更新要依赖前面的,正着来会出现加多次的情况

初始化时的一些要求:

初始化时 f [ 0 ] = 0 ; f [ 1... V ] = − ∞ f[0] = 0; f[1...V] = -∞ f[0]=0;f[1...V]=,-∞一般用-INF且INF = 0x3f3f3f3f; // 在Java中,注意这里的无穷大不要用系统自带的Integer.MIN_VALUE 或者 Integer.MAX_VALUE
如果要求背包装满,答案就是 f [ V ] f[V] f[V],如果可以不装满,答案就是$max \lbrace f[1…V] \rbrace $

完全背包问题

有N件物品和一个容量为V的背包,第i件物品的体积是v[i]、价值是w[i],每种物品都可以无限次使用,求将哪些物品放入背包可以使得价值总和最大

状态定义:

假设第i个物品被选择了k次,易知k的范围为 0 ≤ k ∗ v [ i ] ≤ j 0 ≤ k * v[i] ≤ j 0kv[i]j,相对于背包的选或不选,这里需要for循环不断更新选择的次数k,因此,动态规划的状态表达式就变成了

f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j − k ∗ v [ i ] ] + k ∗ w [ i ] ∣ 0 ≤ k ∗ v [ i ] ≤ j ) f[i][j] = max(f[i - 1][j - k * v[i]] + k * w[i] | 0 ≤ k * v[i] ≤ j) f[i][j]=max(f[i1][jkv[i]]+kw[i]∣0kv[i]j) 含义是第i个物品选k次,从剩余的i-1个物品中选取物品来满足容量 j − k ∗ v [ i ] j - k * v[i] jkv[i]的限制

时间复杂度为 O ( N V ∗ ∑ ( V / v [ i ] ) ) O(NV * ∑(V/v[i])) O(NV(V/v[i])),已经是相当大了,类比下01背包的二维降为一维的解法,看看能不能优化

实际只需要把01背包问题的V的倒向循环改成正向循环即可,代码如下:

for(int i = 1; i <=n; i++) {
    for(int j = v[i]; j <= V; j++) { // 这里的j为什么是从小到大呢?下面会讲解地
        f[j] = Math.max(f[j], f[j - v[i]] + w[i]);
    }
}

V为什么是从小到大呢?因为每次算f[j]的时候,f[j - v[i]]表示地是用前i个物品(可能已经拿过第i个物品了,j从小到大正好可以在本轮多次更新f[j]的值)凑出体积为j - v[i]的最大价值,此时不仅空间复杂度变成了 O ( V ) O(V) O(V),时间复杂度也变成了 O ( N V ) O(NV) O(NV)

01背包和完全背包问题对比

01背包与完全背包的对比

2 多重背包问题二进制拆分优化

多重背包问题

有N件物品和一个容量为V的背包,第i件物品的体积是v[i]、价值是w[i],每种物品只可以使用a[i]次,求将哪些物品放入背包可以使得价值总和最大

多重背包问题和01背包问题、完全背包问题的不同之处在于指定了第i个物品的使用次数,即每个物品的使用次数都可能是不同的

状态分析

f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j − k ∗ v [ i ] ] + k ∗ w [ i ] ∣ 0 ≤ k ∗ v [ i ] ≤ a [ i ] ) f[i][j] = max(f[i - 1][j - k * v[i]] + k * w[i] | 0 ≤ k * v[i] ≤ a[i]) f[i][j]=max(f[i1][jkv[i]]+kw[i]∣0kv[i]a[i])

时间复杂度为 O ( N V ∗ ∑ ( a [ i ] ) ) O(NV * ∑(a[i])) O(NV(a[i]))了,需要优化

多重背包问题二进制拆分优化

拿上面的题的具体化举例,比如一个物品最多有15个可用,15的二进制是1111,那么可以把这个物品拆成4份,分别代表1个物品、2个物品、4个物品、8个物品,此时不管选出0~15中的任何数量个,都可以由这4份组合出来(二进制的理解)

这样上面的4份,每份要么选要么不选,显然成了一个新的01背包问题用01背包的思路解决问题接口重复log a[i]次01背包问题即可

但是上面的15正好可以拆分各位都是1的二进制,如果问题改成一个物品最多有12个可用,12的二进制是1100,按照上面的思路可以拆成1100,即8个和4个,这两个数并不能组成1~12之间所有的数

因此对于任何a[i],我们都先拆成1个( 2 0 2^0 20)、2个( 2 1 2^1 21)、4个( 2 2 2^2 22)、…、 2 k − 1 2^{k-1} 2k1个、 a [ i ] − 2 k − 1 a[i] - 2^{k-1} a[i]2k1 个,k是满足 2 k − 1 < a [ i ] 2^k-1 < a[i] 2k1<a[i]的最大值。这种拆分就可以拼出0~a[i]内的任何值了(即多重背包选择i的个数)。比如1[i]=12,则k最大为3,则a[i]就可以拆分成1、2、4、5

通过上面的二进制拆分,对于一个最大可用数为a[i]的物品来说,我们就把它拆分了 l o g ( a [ i ] ) log(a[i]) log(a[i])个只能用一次的物品,体积为 k v [ i ] kv[i] kv[i],价值为 k w [ i ] kw[i] kw[i](k表示倍数,例如12拆分成1、2、4、5,那么k就依次为1、2、4、5,和上面的k不是一个意思),这样问题就变成了01背包问题,只是物品数量变成了 ∑ i = 1 N l o g ( a [ i ] ) \sum_{i=1}^{N}log(a[i]) i=1Nlog(a[i])个,时间复杂度是O( V ∑ i = 1 N l o g ( a [ i ] ) V\sum_{i=1}^{N}log(a[i]) Vi=1Nlog(a[i]))

3 多重背包问题单调队列优化

上一节得到的多重背包问题的公式为: f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j − k ∗ v [ i ] ] + k ∗ w [ i ] ∣ 0 ≤ k ∗ v [ i ] ≤ a [ i ] ) f[i][j] = max(f[i - 1][j - k * v[i]] + k * w[i] | 0 ≤ k * v[i] ≤ a[i]) f[i][j]=max(f[i1][jkv[i]]+kw[i]∣0kv[i]a[i]),含义是第i个物品选k次,从剩余的i-1个物品中选取物品来满足容量 j − k ∗ v [ i ] j - k * v[i] jkv[i]的限制。遍历k的所有取值来找价值是多少

利用模和取余进行状态表达式简化

对于f[i][j](从i个物品中选取物品填满容量为j的容器,其最大价值是f[i][j])。k的范围受两个因素影响,一是a[i]表示k的上限,此外还要$ j - k * v[i] > 0 即 即 k < j / v[i] ,所以 k 的上限为两者的较小值,我们设物品个数为 i 时 k 的范围为 b [ i ] ,那么 ,所以k的上限为两者的较小值,我们设物品个数为i时k的范围为b[i],那么 ,所以k的上限为两者的较小值,我们设物品个数为ik的范围为b[i],那么b[i] = min(a[i], j / v[i])$

此时状态表达式变为: f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j − k ∗ v [ i ] ] + k ∗ w [ i ] ∣ 0 ≤ k ≤ b [ i ] ) f[i][j] = max(f[i - 1][j - k * v[i]] + k * w[i] | 0 ≤ k ≤ b[i]) f[i][j]=max(f[i1][jkv[i]]+kw[i]∣0kb[i])

下面观察f[i][j]会从哪些状态转移过来:

对于 j − k ∗ v [ i ] ∣ 0 ≤ k ≤ b [ i ] { j - k * v[i] | 0 ≤ k ≤ b[i] } jkv[i]∣0kb[i] j − k ∗ v [ i ] j - k * v[i] jkv[i]模v[i]的余数都是相等的(因为k取不同的值结果都是差v[i]的整数倍)

我们令余数 m o d = j mod = j % v[i] mod=j,除数 d i v = j / v [ i ] div = j / v[i] div=j/v[i],那么j可以表示为 j = d i v ∗ v [ i ] + m o d j = div * v[i] + mod j=divv[i]+mod,替换f[i][j]里面的j可以得到 f [ i ] [ j ] = m a x ( f [ i − 1 ] [ d i v ∗ v [ i ] + m o d − k ∗ v [ i ] ] + k ∗ w [ i ] ∣ 0 ≤ k ≤ b [ i ] ) f[i][j] = max(f[i - 1][div * v[i] + mod - k * v[i]] + k * w[i] | 0 ≤ k ≤ b[i]) f[i][j]=max(f[i1][divv[i]+modkv[i]]+kw[i]∣0kb[i]) = m a x ( f [ i − 1 ] [ m o d + ( d i v − k ) ∗ v [ i ] ] + k ∗ w [ i ] ∣ 0 ≤ k ≤ b [ i ] ) = max(f[i - 1][mod + (div - k) * v[i]] + k * w[i] | 0 ≤ k ≤ b[i]) =max(f[i1][mod+(divk)v[i]]+kw[i]∣0kb[i])

我们假设 k ′ = d i v − k k' = div - k k=divk,因为 0 ≤ k ≤ b [ i ] 0 ≤ k ≤ b[i] 0kb[i],即 0 ≤ ( d i v − k ′ ) ≤ b [ i ] 0 ≤ (div - k') ≤ b[i] 0(divk)b[i],移项可得 k ′ k' k的范围是 ( d i v − b [ i ] ) ≤ k ′ ≤ d i v (div - b[i]) ≤ k' ≤ div (divb[i])kdiv,k’带入上面的式子,可以得到 f [ i ] [ j ] = m a x ( f [ i − 1 ] [ m o d + k ′ ∗ v [ i ] ] + ( d i v − k ′ ) ∗ w [ i ] ∣ d i v − b [ i ] ≤ k ′ ≤ d i v ) f[i][j] = max(f[i - 1][mod + k' * v[i]] + (div - k') * w[i] | div - b[i] ≤ k' ≤ div) f[i][j]=max(f[i1][mod+kv[i]]+(divk)w[i]divb[i]kdiv)

把div * w[i] 单独拿出来,得到 f [ i ] [ j ] = m a x ( f [ i − 1 ] [ m o d + k ′ ∗ v [ i ] ] − k ′ ∗ w [ i ] ∣ ( d i v − b [ i ] ) ≤ k ′ ≤ d i v ) + d i v ∗ w [ i ] f[i][j] = max(f[i - 1][mod + k' * v[i]] - k' * w[i] | (div - b[i]) ≤ k' ≤ div) + div * w[i] f[i][j]=max(f[i1][mod+kv[i]]kw[i](divb[i])kdiv)+divw[i],这里k’批量换成k(就是个符号表示,和原来的k不是一个k),得到 f [ i ] [ j ] = m a x ( f [ i − 1 ] [ m o d + k ∗ v [ i ] ] − k ∗ w [ i ] ∣ ( d i v − b [ i ] ) ≤ k ≤ d i v ) + d i v ∗ w [ i ] f[i][j] = max(f[i - 1][mod + k * v[i]] - k * w[i] | (div - b[i]) ≤ k ≤ div) + div * w[i] f[i][j]=max(f[i1][mod+kv[i]]kw[i](divb[i])kdiv)+divw[i]

考虑{mod, mod + v[i], mod + 2* v[i] + mod + 3 * v[i], …, j},可以知道f[i][j]就是求j前面的 b [ i ] + 1 b[i]+1 b[i]+1(为什么是b[i] + 1?因为b[i]是V/v[i]和a[i]的较小值,是真实的第i个物品选取个数的上限)个数对应的 f [ i − 1 ] [ m o d + k ∗ v [ i ] ] − k ∗ w [ i ] f[i - 1][mod + k * v[i]] - k * w[i] f[i1][mod+kv[i]]kw[i]的最大值

对于最外层i的枚举,b[i]+ 1是固定的,因此问题转化为求一个固定长度的滑窗内的最大值,显然可以用单调队列来优化。

我们可以维护一个单调下降的队列,每次加入的时候加入到队尾,保证队头到队尾单调递减。每次弹出队头,直到满足队头在滑窗内,队头的值就是这个滑窗内的最大值,这样这一步就是线性的。

我们枚举mod,再枚举div,求f[i][j]是用单调队列,总共是O(V)的,总的时间复杂度仍然保持在了O(NV)

4~7 知识精练

CodeForces 366C Dima And Salad

有n个数对(a[i], b[i]),现在要求去除若干个数对使得 s u m ( a [ i ] ) / s u m ( b [ i ] ) = k sum(a[i]) / sum(b[i]) = k sum(a[i])/sum(b[i])=k,你需要最大化sum(a[i]),其中 n ≤ 100 , k ≤ 10 , a [ i ] ≤ 100 n ≤ 100, k ≤ 10, a[i] ≤ 100 n100,k10,a[i]100

比如下面的输入表示输入3(n的值)个数对, s u m ( a [ i ] ) / s u m ( b [ i ] ) = 2 = k sum(a[i]) / sum(b[i]) = 2 = k sum(a[i])/sum(b[i])=2=k,a[1]~a[3]为10 8 1,b[1]~b[3]为2 7 1

3 2
10 8 1
2 7 1

输出为

18

因为 ( a [ 1 ] + a [ 2 ] ) / ( b [ 1 ] + b [ 2 ] ) = ( 10 + 8 ) / ( 2 + 7 ) = 2 = k (a[1] + a[2])/(b[1] + b[2]) = (10 + 8) / (2 + 7) = 2 = k (a[1]+a[2])/(b[1]+b[2])=(10+8)/(2+7)=2=k s u m ( a [ i ] ) = 10 + 8 = 18 sum(a[i]) = 10 + 8 = 18 sum(a[i])=10+8=18

再如下面的输入:

5 3 // 一共5对数,sum(a[i]) / sum(b[i]) = 3
4 4 4 4 4
2 2 2 2 2

输出为:

-1

因为上面的 s u m ( a [ i ] ) / s u m ( b [ i ] ) sum(a[i])/sum(b[i]) sum(a[i])/sum(b[i])显然只能为2,不可能出现3

题目转换

观察 s u m ( a [ i ] ) / s u m ( b [ i ] ) = k sum(a[i]) / sum(b[i]) = k sum(a[i])/sum(b[i])=k,移项可得 s u m ( a [ i ] ) = s u m ( b [ i ] ) ∗ k sum(a[i]) = sum(b[i]) * k sum(a[i])=sum(b[i])k,再移项可得 s u m ( a [ i ] ) − s u m ( b [ i ] ) ∗ k = 0 sum(a[i]) - sum(b[i]) * k = 0 sum(a[i])sum(b[i])k=0,合并下可以得到 s u m ( a [ i ] − k ∗ b [ i ] ) = 0 sum(a[i] - k * b[i]) = 0 sum(a[i]kb[i])=0

至此,问题也就转换成了第i个物体的体积是 v [ i ] = a [ i ] − k ∗ b [ i ] v[i] = a[i] - k * b[i] v[i]=a[i]kb[i],价值是a[i],的背包问题,为了防止总的体积被v[i]减成负的(j-v[i]要大于0),所以初始化时j要足够大,我们取10000,这样我们就可以分成正负两个f来做了

本问题即是求1~i对应的a和b放入后 ∑ i = 1 i v [ i ] \sum_{i=1}^{i}v[i] i=1iv[i]是0,且 s u m ( a [ i ] ) = ∑ i = 1 i a [ i ] sum(a[i])=\sum_{i=1}^{i}a[i] sum(a[i])=i=1ia[i]即放入的所有的物体i的价值和最大)

java实现如下

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner s = new Scanner(System.in);
        int n = s.nextInt();
        int k = s.nextInt();
        int[] a = new int[n + 1];
        int[] b = new int[n + 1];
        int[] v = new int[n + 1];
        for (int i = 1; i <= n; i++) a[i] = s.nextInt();
        for (int i = 1; i <= n; i++) b[i] = s.nextInt();
        int N = 10000, INF = 0x3f3f3f3f; // 这里的无穷大不要用系统自带的Integer.MIN_VALUE
        int[] f1 = new int[N + 1];
        int[] f2 = new int[N + 1];
        Arrays.fill(f1, -INF);
        Arrays.fill(f2, -INF);
        f1[0] = 0;
        f2[0] = 0;
        int[] c = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            v[i] = a[i] - k * b[i];
            if (v[i] >= 0) {
                // j初始化10000因为要保证 j≥v[i]的条件不成立前i要能循环完,因此j要足够大
                for (int j = N; j >= v[i]; j--) f1[j] = Math.max(f1[j], f1[j - v[i]] + a[i]); // 正体积时的背包
            } else {
                // 处理v[i]小于0的情况
                v[i] = -v[i];
                for (int j = N; j >= v[i]; j--) f2[j] = Math.max(f2[j], f2[j - v[i]] + a[i]); // 负体积时的背包
            }
        }
        int res = -1;
        for (int i = 0; i <= N; i++) {
            res = Math.max(res, f1[i] + f2[i]);
        }
        System.out.println(res == 0 ? -1 : res); // 和为0表示没有满足条件的背包
    }
}

CF864E Fire

题目分析

是个典型的01背包问题,这里的花费不是体积,而是时间。用f[i]表示到i时刻最大能救多少价值,j表示第j个物品救还是不救

f [ i ] = m a x ( f [ i ] , f [ i − t [ j ] ] + p [ j ] ) f[i] = max(f[i], f[i - t[j]] + p[j]) f[i]=max(f[i],f[it[j]]+p[j]) 其中 ( i − t [ j ] ) ≤ d [ j ] (i - t[j]) ≤ d[j] (it[j])d[j]否则第j个物品就开始燃烧而没法抢救了

因此先对物品按照d[i]进行排序,然后依次更新,即按照燃烧开始的时刻早晚依次开始抢救

代码如下:参考地 https://www.luogu.com.cn/blog/213870/solution-cf864e

这道题很明显是一个01背包。

第一问:先将所有数据存在结构体里,按 d[i] 排序,然后当成普通01背包做,在第二层循环时边界调整为 d[i] 即可。

转移方程: f [ i ] = m a x ( f [ i ] , f [ i − t [ i ] ] + p [ i ] ) f[i]=max(f[i], f[i - t[i]] + p[i]) f[i]=max(f[i],f[it[i]]+p[i])

对于第二问,每一个 f[i] 都有一个对应的数组储存最优解下取的物品。

注意:第 i 件物品在 d[i] 秒彻底焚毁,所以第二层循环只能循环到 d [ i ] − 1 d[i] − 1 d[i]1

代码:

#include<bits/stdc++.h>

using namespace std;
struct Item { //结构体,p,t,d作用于题面里相同,num储存编号
    int p, t, d, num;
} a[110];
int n, f[2010], ans, ansnum; //ans储存最大价值,ansnum储存最大价值对应的f[i]的编号
vector<int> v[2010]; // 列表数组,v[i]表示f[i]时刻抢到的东西的列表

bool cmp(Item i1, Item i2) {
    return i1.d < i2.d; //按d排序
}

int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i].t >> a[i].d >> a[i].p;
        a[i].num = i;
    }
    sort(a + 1, a + n + 1, cmp);
    for (int i = 1; i <= n; i++)
        for (int j = a[i].d - 1; j >= a[i].t; j--) // 只能取一次,倒序循环(从a[i].d-1开始!)
            if (f[j - a[i].t] + a[i].p > f[j]) {
                f[j] = f[j - a[i].t] + a[i].p; // 转移方程
                v[j] = v[j - a[i].t]; // 把上一个抢东西的时刻抢到的东西列表复制过来(注意是深度拷贝)
                v[j].push_back(a[i].num); // 更新v[j],把本轮新抢的东西加入进去
            }
    for (int i = 0; i <= 2000; i++) { // 循环获取抢救到最大价值的时刻
        if (f[i] > ans) {
            ans = f[i];
            ansnum = i;
        }
    }
    cout << ans << endl << v[ansnum].size() << endl;
    for (int i : v[ansnum]) cout << i << " "; // 打印获取的最大价值时的抢到了哪些物品
    return 0;
}

Java实现如下:C++的vector和Java的List使用时有很大差异的,前者赋值会自动拷贝,后者赋值时引用赋值

import java.util.*;

public class Main {
    static class Item implements Comparable<Item> {
        int p, t, d, num;

        // 构造函数的参数顺序务必正确
        public Item(int t, int d, int p, int num) {
            this.t = t;
            this.d = d;
            this.p = p;
            this.num = num;
        }

        @Override
        public int compareTo(Item o) {
            return d - o.d;
        }
    }

    static int n, ans, ansnum;
    static List<Integer>[] v = new List[2010]; // 列表数组,v[i]表示f[i]时刻抢到的东西的列表
    static int[] f = new int[2010]; // f[i]表示到i时刻最大能救多少价值, i在[0, 2000],为了防止边界问题多取一点

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        Item[] a = new Item[n + 1];
        for (int i = 1; i <= n; i++) a[i] = new Item(sc.nextInt(), sc.nextInt(), sc.nextInt(), i);
        Arrays.sort(a, 1, n + 1);
        for (int i = 0; i < v.length; i++) v[i] = new ArrayList<>();
        v[0].add(1);
        for (int i = 1; i <= n; i++) {
            for (int j = a[i].d - 1; j >= a[i].t; j--) { // 只能取一次,倒序循环(从a[i].d-1开始!)
                if (f[j - a[i].t] + a[i].p > f[j]) {
                    f[j] = f[j - a[i].t] + a[i].p; // 转移方程
                    v[j] = new ArrayList<>();
                    for (int e : v[j - a[i].t]) v[j].add(e); // 把上一个抢东西的时刻抢到的东西列表复制过来(注意是深度拷贝)
                    v[j].add(a[i].num); // 更新v[j],把本轮新抢的东西加入进去
                }
            }
        }
        for (int i = 0; i <= 2000; i++) { // 循环获取抢救到最大价值的时刻
            if (f[i] > ans) {
                ans = f[i];
                ansnum = i;
            }
        }
        System.out.println(ans);
        System.out.println(v[ansnum].size() - 1);
        for (int i = 1; i < v[ansnum].size(); i++) System.out.print(v[ansnum].get(i) + " "); // 打印获取的最大价值时的抢到了哪些物品
    }
}

POJ1742 Coins,也是AcWing 281.硬币

题意:有n种面额的硬币。面额、个数分别为v[i]、c[i],求最多能搭配出几种不超过m的金额?

思路:f[i][j]就是总数为j的价值金额是否已经有了含有面额i的硬币可以满足这种搭配方法,如果现在没有,那么我们就一个个硬币去尝试直到有,这种价值方法有了的话,那么就是总方法数加1。

显然是个多重背包可行性问题,背包的体积V在这里是m,每个背包的体积是v[i],每个背包的使用个数最多是c[i]个,直接套用多重背包的代码模板很好解决这个问题,简化成f[j]即可

#include <cstdio>
#include <cstring>

using namespace std;

int f[100005]; //表示当前i价格是否出现过,1表示出现出,0表示没有可以拼接的方案
int sum[100005];//当价格达到i时,最多使用这一种硬币的次数
int v[105], c[105];

int main() {
    int i, j, n, m;
    while (~scanf("%d%d", &n, &m), n + m) {
        for (i = 1; i <= n; i++) scanf("%d", &v[i]);
        for (i = 1; i <= n; i++) scanf("%d", &c[i]);
        memset(f, 0, sizeof(f));
        f[0] = 1;
        int ans = 0;
        for (i = 1; i <= n; i++) { // i表示硬币的面额
            memset(sum, 0, sizeof(sum));// 关键是用sum来限定了次数,sum[i]表示为了拼出金额j用了几个面额i
            for (j = v[i]; j <= m; j++) { // 多重背包从小到大更新
                if (!f[j] && f[j - v[i]] && sum[j - v[i]] < c[i]) { // 如果j价格没有出现过,且j-v[i]出现过,并且使用i硬币的次数没有超出给定的数量
                    f[j] = 1; // 1表示j这种金额可以拼出来了
                    sum[j] = sum[j - v[i]] + 1;// 使用次数+1
                    ans++; // 小于金额m的所有的拼的方案都要累计
                }
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

Java实现如下,注意一开始按照用例的上限固定好数组的大小要比根据数据动态分配省内存,否则容易超出程序的内存限制

// 单调队列,单调栈和单调队列的理解见博客:https://blog.csdn.net/shxifs/article/details/101058167

import java.io.*;
import java.util.*;

public class Main {
    static int[] f = new int[100005]; // f[j]表示当前j价金额是否出现过,1表示出现出,0表示没有可以拼接的方案
    static int[] sum = new int[100005]; // sum[j]表示当金额为j时时,最多使用第i种硬币的次数
    static int[] v = new int[105]; // v[i]表示硬币i的面额
    static int[] c = new int[105]; // c[i]表示硬币i的个数

    public static void main(String[] args) throws IOException {
        Scanner sc = new Scanner(System.in);
        while (sc.hasNext()) {
            int n = sc.nextInt(), m = sc.nextInt();
            if(n == 0 && m == 0) return;
            Arrays.fill(f, 0);
            for (int i = 1; i <= n; i++) v[i] = sc.nextInt();
            for (int i = 1; i <= n; i++) c[i] = sc.nextInt();
            f[0] = 1; // 面额为0不用拼接,肯定可以
            int ans = 0;
            for (int i = 1; i <= n; i++) { // i表示硬币的面额
                Arrays.fill(sum, 0);
                for (int j = v[i]; j <= m; j++) { // 多重背包从小到大更新
                    if (f[j] == 0 && f[j - v[i]] == 1 && sum[j - v[i]] < c[i]) { // 如果j价格没有出现过,且j-v[i]出现过,并且使用i硬币的次数没有超出给定的数量
                        f[j] = 1; // 1表示j这种金额可以拼出来了
                        sum[j] = sum[j - v[i]] + 1;// 使用次数+1
                        ans++; // 小于金额m的所有的拼的方案都要累计
                    }
                }
            }
            System.out.println(ans);
        }
    }
}

CodeForces 755F PolandBall And Gifts

题解:https://www.luogu.com.cn/problem/solution/CF755F

如何最小化收不到礼物的人数?

  • 对于一个偶环,假设长度为k,那么只要有k/2个人忘带礼物,k个人就全都收不到礼物
  • 对于一个奇环,假设长度为k,那么需要有(k + 1)/2个人忘带礼物,k个人就全都收不到礼物
    贪心即可

如何最小化收不到礼物的人数?

  • 如果有一个大小为m的环,只要让这m个人都忘带,就只有m个人收不到礼物(这样一个人就只影响了一个人,比前面一个人影响两个人要好得多)。因此如果能找到若干个环,使得他们的长度之和刚好是k(这里可以看出来是个背包问题),那么答案就是k
  • 否则会有一个环不能被完全覆盖,还会再牵连一个人,答案就是k+1

现在问题变成了有m个环,第i个环的长度为L[i],该长度的环一共有C[i]个,问能否凑出长度为k的方案。显然是多重背包问题!

用二进制分组,拆分成log(C[i])个物品,做01背包,时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

// 参考:https://www.luogu.com.cn/problem/solution/CF755F 第2个题解
#include<iostream>
#include<algorithm>
#include<cstdio>

using namespace std;

int a[1000005]; // a[i]表示图中i指向的下一个点(一个人的礼物要送给哪一个人)
int huan[1000005];  // huan[i]表示第i个环的长度(和下面的i进行区分,这里是个环不管长度是否相等都算一个i)
int shu[1000005]; // shu[i]表示第i个不同长度的环在多重背包中能用几次,即把相同长度的环进行计数。i表示第i个不同长度的环
int re[1000005]; // re[i]表示第i个不同长度的环的长度是多少
int opt[1000005]; // opt[i]表示01背包中第i个背包的容量,题目中表示第i个不同长度环二进制拆分后累计的个数
bool vis[1000005]; // DFS中记录被访问过地点,防止重复计算
bool dp[1000005]; // dp[i]表示i个人忘带礼物时,能否只影响i个人

/**
 * DFS获取环的长度(环内点的个数)
 * @param now 当前的人
 * @param from 根节点,即环遍历的起点
 * @param dep 环的长度(点的个数)
 */
int dfs(int now, int from, int dep) {
    if (now == from) return dep; // DFS回到起点说明环遍历完了,返回环的长度
    else {
        vis[now] = true;
        dfs(a[now], from, dep + 1); // DFS遍历下now的一个邻接点(即第now个人忘带礼物影响的另一个人)
    }
}

int main() {
    dp[0] = 1;
    int n, k;
    cin >> n >> k;
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);

    // 1.DFS获取每个环内点的个数
    int cnt = 0;
    for (int i = 1; i <= n; i++) {
        if (vis[i] == 0) huan[++cnt] = dfs(a[i], i, 1);
    }

    // 2.求最大值,即一个人尽量影响两个人  ------ 贪心,先按照环的长度从小到大排序
    sort(huan + 1, huan + cnt + 1);
    int maxans = 0; // 求最大值,初始化为最小值
    int left = k; // 剩下忘带的人
    int weipipei = 0; // 奇环上未匹配的人数
    for (int i = 1; i <= cnt; i++) { // 遍历所有的环
        // 尽可能先让一个人忘带影响两个人,这样才能最大化
        if (huan[i] / 2 <= left) { // 环的长度不够分配忘带的人
            left -= huan[i] / 2; // 当前的环分配环长度的一半的人
            maxans += huan[i] / 2 * 2; // 最大的分配不到的人就是环长度的最近偶数值
        } else {
            maxans += left * 2; // 剩余的未带的人正好能全部放进当前的环里,那么最大值加上2倍的剩余未匹配的人即可
            left = 0; // 剩下的忘带的人直接归零了,
            break; // 可以直接退出了,这一步是防止超时的关键,没有这一步肯定TLE
        }
        if (huan[i] & 1) weipipei++; // 奇数环处理:每个奇数环都会有一个人剩下无法被影响
    }
    maxans += min(left, weipipei); // 剩下的忘带的人(left)可以使用一个人抵消一个人的策略来处理未分配的人(weifenpei),所以需要取二者的较小值

    // 3.求最小值,即一个人尽量影响一个人,多重背包,够分就表明能做到,否则就得+1
    int temp = 0;
    for (int i = 1; i <= cnt; i++) {
        if (huan[i] == huan[i - 1]) shu[temp]++; // 对相同长度的环进行计数,因为先按照环长度进行排序了,所以不断检查相邻的huan[i]是否相等即可
        else {
            shu[++temp]++; // 一旦环长度变了,就开始新一轮的计数
            re[temp] = huan[i]; // 初次需要把第i个不同长度的环的长度存下来
        }
    }

    // 3.1 多重背包的二进制优化
    cnt = 0;
    for (int i = 1; i <= temp; i++) {
        for (int j = 1; shu[i] >
                        0; j <<= 1) { // j <<= 1表示不断乘以2,比如1、2、4、8...都放到01背包的opt[cnt]中,直到shu[i]不够再分,shu[i]减去此时的j的结果放到opt[cnt]即可
            // leftt表示01背包问题中第cnt个物品的大小,这里是使用次数的二进制拆分
            int leftt = min(shu[i], j); // 看看二进制拆分还够不够分,够分就接着取不断扩大2倍的j,否则就取shu[i]减去前面的1+2+4+...后剩下的值
            opt[++cnt] = leftt * re[i]; // re[i]表示第i个不同长度环的长度,leftt表示二进制拆分后当前可以用几次,二者相乘才是背包的当前轮次消耗值
            shu[i] -= leftt;
        }
    }

    // 3.2 01背包问题解决,总的容量V在题目里为k,即一共忘带的人数;第i个要放入的物品的体积为opt[i],即题目中第i个环二进制拆分后每个可以放入的总人数
    for (int i = 1; i <= cnt; i++) {
        for (int j = k; j >= opt[i]; j--) {
            if (dp[j - opt[i]]) dp[j] = true; // j - opt[i]的背包能拼出来,那么加上当前的opt[i]的物品肯定能占满容量为j的背包
        }
    }
    int minans = k; // 先默认全能拼出来,即k个人不带礼物只会影响到k个人(即这k个人都能正好分配到不同的环中)
    if (!dp[k])minans++; // 第k个人拼不出来,说明还要再多影响一个人,即要+ 1
    cout << minans << ' ' << maxans << endl;
}

Java实现如下:

// 单调队列,单调栈和单调队列的理解见博客:https://blog.csdn.net/shxifs/article/details/101058167
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {
    static final int N = 1000005;
    static int[] a = new int[N]; // a[i]表示图中i指向的下一个点(一个人的礼物要送给哪一个人)
    static int[] huan = new int[N];  // huan[i]表示第i个环的长度(和下面的i进行区分,这里是个环不管长度是否相等都算一个i)
    static int[] shu = new int[N]; // shu[i]表示第i个不同长度的环在多重背包中能用几次,即把相同长度的环进行计数。i表示第i个不同长度的环
    static int[] re = new int[N]; // re[i]表示第i个不同长度的环的长度是多少
    static int[] opt = new int[N]; // opt[i]表示01背包中第i个背包的容量,题目中表示第i个不同长度环二进制拆分后累计的个数
    static boolean[] vis = new boolean[N]; // DFS中记录被访问过地点,防止重复计算
    static boolean[] dp = new boolean[N]; // dp[i]表示i个人忘带礼物时,能否只影响i个人

    /**
     * DFS获取环的长度(环内点的个数)
     *
     * @param now  当前的人
     * @param from 根节点,即环遍历的起点
     * @param dep  环的长度(点的个数)
     */
    static int dfs(int now, int from, int dep) {
        if (now == from) return dep; // DFS回到起点说明环遍历完了,返回环的长度
        else {
            vis[now] = true;
            return dfs(a[now], from, dep + 1); // DFS遍历下now的一个邻接点(即第now个人忘带礼物影响的另一个人)
        }
    }

    public static void main(String[] args) throws IOException {
        BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        String[] nk = bf.readLine().split(" ");
        int n = Integer.parseInt(nk[0]), k = Integer.parseInt(nk[1]);
        String[] chs = bf.readLine().trim().split(" ");
        for (int i = 1; i <= n; i++) a[i] = Integer.parseInt(chs[i - 1]);

        // 1.DFS获取每个环内点的个数
        int cnt = 0;
        for (int i = 1; i <= n; i++) {
            if (!vis[i]) huan[++cnt] = dfs(a[i], i, 1);
        }

        // 2.求最大值,即一个人尽量影响两个人  ------ 贪心,先按照环的长度从小到大排序
        Arrays.sort(huan, 1, cnt + 1);
        int maxans = 0; // 求最大值,初始化为最小值
        int left = k; // 剩下忘带的人
        int weipipei = 0; // 奇环上未匹配的人数
        for (int i = 1; i <= cnt; i++) { // 遍历所有的环
            // 尽可能先让一个人忘带影响两个人,这样才能最大化
            if (huan[i] / 2 <= left) { // 环的长度不够分配忘带的人
                left -= huan[i] / 2; // 当前的环分配环长度的一半的人
                maxans += huan[i] / 2 * 2; // 最大的分配不到的人就是环长度的最近偶数值
            } else {
                maxans += left * 2; // 剩余的未带的人正好能全部放进当前的环里,那么最大值加上2倍的剩余未匹配的人即可
                left = 0; // 剩下的忘带的人直接归零了,
                break; // 可以直接退出了,这一步是防止超时的关键,没有这一步肯定TLE
            }
            if ((huan[i] & 1) == 1) weipipei++; // 奇数环处理:每个奇数环都会有一个人剩下无法被影响
        }
        maxans += Math.min(left, weipipei); // 剩下的忘带的人(left)可以使用一个人抵消一个人的策略来处理未分配的人(weifenpei),所以需要取二者的较小值

        // 3.求最小值,即一个人尽量影响一个人,多重背包,够分就表明能做到,否则就得+1
        int temp = 0;
        for (int i = 1; i <= cnt; i++) {
            if (huan[i] == huan[i - 1]) shu[temp]++; // 对相同长度的环进行计数,因为先按照环长度进行排序了,所以不断检查相邻的huan[i]是否相等即可
            else {
                shu[++temp]++; // 一旦环长度变了,就开始新一轮的计数
                re[temp] = huan[i]; // 初次需要把第i个不同长度的环的长度存下来
            }
        }

        // 3.1 多重背包的二进制优化
        cnt = 0;
        for (int i = 1; i <= temp; i++) {
            for (int j = 1; shu[i] >
                    0; j <<= 1) { // j <<= 1表示不断乘以2,比如1、2、4、8...都放到01背包的opt[cnt]中,直到shu[i]不够再分,shu[i]减去此时的j的结果放到opt[cnt]即可
                // leftt表示01背包问题中第cnt个物品的大小,这里是使用次数的二进制拆分
                int leftt = Math.min(shu[i], j); // 看看二进制拆分还够不够分,够分就接着取不断扩大2倍的j,否则就取shu[i]减去前面的1+2+4+...后剩下的值
                opt[++cnt] = leftt * re[i]; // re[i]表示第i个不同长度环的长度,leftt表示二进制拆分后当前可以用几次,二者相乘才是背包的当前轮次消耗值
                shu[i] -= leftt;
            }
        }

        // 3.2 01背包问题解决,总的容量V在题目里为k,即一共忘带的人数;第i个要放入的物品的体积为opt[i],即题目中第i个环二进制拆分后每个可以放入的总人数
        dp[0] = true;
        for (int i = 1; i <= cnt; i++) {
            for (int j = k; j >= opt[i]; j--) {
                if (dp[j - opt[i]]) dp[j] = true; // j - opt[i]的背包能拼出来,那么加上当前的opt[i]的物品肯定能占满容量为j的背包
            }
        }
        int minans = k; // 先默认全能拼出来,即k个人不带礼物只会影响到k个人(即这k个人都能正好分配到不同的环中)
        if (!dp[k]) minans++; // 第k个人拼不出来,说明还要再多影响一个人,即要+ 1
        System.out.println(minans + " " + maxans);
    }
}

NOIP2014 Day1 飞扬的小鸟,也即AcWing 512.飞扬的小鸟

题解:https://www.luogu.com.cn/problem/solution/P1941 其中比较好的题解:https://www.luogu.com.cn/blog/YRwtm/post-2014noip-ti-gao-zu-d1t3-post

一个简单的思路:用f[i][j]表示第i列第j的高度所需要的最小点击次数。

枚举在第i-1列点k次屏幕,用 f [ i − 1 ] [ j − k ∗ u p [ i − 1 ] ] + k f[i-1][j - k * up[i - 1]] + k f[i1][jkup[i1]]+k来更新 f [ i ] [ j ] f[i][j] f[i][j] k ∗ u p [ i − 1 ] k * up[i - 1] kup[i1]表示第i-1列点击k次。

也就是当成多重背包来做,这样的时间复杂度时 O ( n m 2 ) O(nm^2) O(nm2),只能得到70分,需要使用单调队列来优化,才能做到 O ( n m ) O(nm) O(nm)

更简单的做法:可以把点屏幕当成完全背包来做:

  • 先跳到下一个位置,用多重背包: f [ i ] [ j ] = m i n ( f [ i ] [ j ] , f [ i − 1 ] [ j − u p [ i − 1 ] ] + 1 ) f[i][j] = min(f[i][j], f[i - 1][j - up[i - 1]] + 1) f[i][j]=min(f[i][j],f[i1][jup[i1]]+1)
  • 到下一个未知了,假设可以跳无限次,就成了完全背包: f [ i ] [ j ] = m i n ( f [ i ] [ j ] , f [ i ] [ j − u p [ i − 1 ] ] + 1 ) f[i][j] = min(f[i][j], f[i][j - up[i - 1]] + 1) f[i][j]=min(f[i][j],f[i][jup[i1]]+1)
    即先假设第i列没有管子,更新完所有的f[i][j]之后,把管子对应的部分的f[i][j]设置为INF即可,此时的时间复杂度为 O ( n m ) O(nm) O(nm)
#include<bits/stdc++.h>

using namespace std;

struct Pipe {
    int pos, up, bottom;
} pipe[10010];

const int MAXN = (1 << 30);
int n, m, k, lift[10010], down[10010], f[10010][1010];

bool cmp(Pipe a, Pipe b) {
    return a.pos < b.pos;
}

int main() {
    scanf("%d%d%d", &n, &m, &k);
    for (int i = 0; i <= n - 1; i++) scanf("%d%d", &lift[i], &down[i]);
    for (int i = 1; i <= k; i++) scanf("%d%d%d", &pipe[i].pos, &pipe[i].bottom, &pipe[i].up);
    sort(pipe + 1, pipe + k + 1, cmp);
    for (int i = 0; i <= n; i++)
        for (int j = 0; j <= m; j++) f[i][j] = MAXN;
    for (int i = 1; i <= m; i++) f[0][i] = 0;
    int nump = 1;
    for (int i = 1; i <= n; i++) {
        int lower = 1, upper = m;
        if (pipe[nump].pos == i) {
            upper = pipe[nump].up - 1;
            lower = pipe[nump++].bottom + 1;
        }
        for (int j = 1; j <= upper; j++) { // 上升
            for (int k = j - lift[i - 1]; k <= m; k++) {
                if (k > j - lift[i - 1] && j < m) break; // 高度不是m就只做一次退出
                if (k >= 1) f[i][j] = min(f[i][j], min(f[i - 1][k], f[i][k]) + 1);
            }
        }
        for (int j = lower; j <= upper; j++) {
            if (j + down[i - 1] <= m) { // 下降
                f[i][j] = min(f[i][j], f[i - 1][j + down[i - 1]]);
            }
        }
        for (int j = 1; j <= lower - 1; j++) f[i][j] = MAXN; // 把柱子原来的最大值填回去
    }
    int minn = MAXN;
    bool flag = false;
    for (int i = n; i >= 1; i--) {
        for (int j = 1; j <= m; j++) {
            if (f[i][j] != MAXN) {
                flag = true;
                minn = min(minn, f[i][j]);
            }
        }
        if (flag) {
            if (i == n) {
                printf("1\n%d", minn);
                return 0;
            } else {
                for (int j = k; j >= 1; j--) {
                    if (i >= pipe[j].pos) {
                        printf("0\n%d", j);
                        return 0;
                    }
                }
            }
        }
    }
    printf("0\n0");
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/492990.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【飞书ChatGPT机器人】飞书接入ChatGPT,打造智能问答助手

文章目录 前言环境列表视频教程1.飞书设置2.克隆feishu-chatgpt项目3.配置config.yaml文件4.运行feishu-chatgpt项目5.安装cpolar内网穿透6.固定公网地址7.机器人权限配置8.创建版本9.创建测试企业10. 机器人测试 转载自远控源码文章&#xff1a;飞书接入ChatGPT - 将ChatGPT集…

Ubuntu 如何查看 CPU 架构、系统信息、内核版本、版本代号?

Ubuntu 查看 CPU 架构、系统信息、内核版本、版本代号等相关信息有很多方式&#xff0c;本文介绍几种常用的命令。 x86 架构与 ARM 架构的 CPU 架构不同&#xff0c;如果回显为 aarch64 表示为 ARM 架构&#xff0c;如果回显为 x86_64 表示为 x86 架构&#xff0c;参考《CPU 架…

Prometheus快速入门

Prometheus快速入门 环境准备 三台主机&#xff0c;配置好主机名 各自配置好主机名 # hostnamectl set-hostname --static server.cluster.com ... 三台都互相绑定IP与主机名 # vim /etc/hosts 192.168.126.143 server.cluster.com 192.168.126.142 agent.clu…

归并排序(看了就会)

目录 概念1. 基本思想2. 实现逻辑3. 复杂度分析4、代码 概念 归并排序&#xff0c;是创建在归并操作上的一种有效的排序算法。算法是采用分治法&#xff08;Divide and Conquer&#xff09;的一个非常典型的应用&#xff0c;且各层分治递归可以同时进行。归并排序思路简单&…

智头条|欧盟达成《人工智能法》协议,全球前沿科技齐聚AWE 2023

行业动态 华为云联手多方推进数字化&#xff0c;软通动力深度参与 华为云宣布启动“‘百城万企’应用现代化中国行”&#xff0c;旨在推动应用现代化进程、助力数字中国高质量落地。软通动力是该行动的参与者之一&#xff0c;共同探索符合区域特点、产业趋势、政企现状的数字化…

Python进阶(Linux操作系统)

一&#xff0c;操作系统 1.1&#xff0c;Linux系统基础操作 1.2&#xff0c;linux进程与线程 1.2.1并发&#xff0c;并行 &#xff08;1&#xff09;并发&#xff1a;在一段时间内交替的执行多个任务&#xff1a;对于单核CPU处理多任务&#xff0c;操作系统轮流让让各个任务…

BasicVSR++代码解读(总体介绍)

本文代码主要来自于OpenMMLab提供的MMEditing开源工具箱中的BasicVSR代码。第一部分的解读主要是针对每一个部分是在做什么提供一个解释&#xff0c;便于后续细读每一个块的细节代码。 &#xff08;1&#xff09;导入库     basicvsr_plusplus_net中主要继承了torch,mmcv,m…

信号的产生——线性调频函数

信号的产生——线性调频函数 产生线性调频扫频信号函数chirp的调用格式如下&#xff1a; &#xff08;1&#xff09;y chirp(t,f0, t1,f1) 功能&#xff1a;产生一个线性&#xff08;频率随时间线性变化&#xff09;信号&#xff0c;其时间轴设置由数组t定义。时刻0的瞬间频…

SpringBoot的配置文件、日志文件

一、配置文件&#xff08; .properties、.yml&#xff09; 1、.properties 配置文件 1.1、格式 1.2、基本语法 1.2.1、如&#xff1a;一般配置&#xff08;以键值的形式配置的&#xff0c;key 和 value 之间是以“”连接的。&#xff09; 1.2.2、如&#xff1a;自定义配置&a…

tcc-transaction 源码分析

tcc介绍 tcc介绍查看我之前的文章&#xff1a; https://caicongyang.blog.csdn.net/article/details/119721282?spm1001.2014.3001.5502 tcc-transaction 介绍&#xff1a; http://mengyun.org/zh-cn/index.html 本文基于2.x 最新版本:https://github.com/changmingxie/tcc…

以京东为例,分析优惠价格叠加规则

一、平行优惠计算原则 1、什么是“平行式门槛计算规则”&#xff1f; 平行式门槛计算规则&#xff0c;即每一层级优惠都直接根据商品的单品基准价来计算是否符合门槛&#xff0c;店铺/平台促销、优惠券类优惠之间是并列关系&#xff0c;只要单品基准价或单品基准价总和&#x…

c++(类和对象中)

【本节目标】 1. 类的6个默认成员函数 2. 构造函数 3. 析构函数 4. 拷贝构造函数 5. 赋值运算符重载 6. const成员函数 7. 取地址及const取地址操作符重载 目录 1、类的6个默认成员函数 2、构造函数 2.1概念 2.2特性 3.析构函数 3.1概念 3.2特性 4.拷贝构造函数…

Kafka生产者

一、生产者发送流程 在消息发送的过程中&#xff0c;涉及到了两个线程——main 线程和 Sender 线程。在 main 线程中创建了一个双端队列 RecordAccumulator。main 线程将消息发送给 RecordAccumulator&#xff0c;Sender 线程不断从 RecordAccumulator 中拉取消息发送到 Kafka …

网络应用基础 ——(2023新星计划文章二)

一&#xff0c;TCP/UDP报头 1.1TCP报文头部详解 Source port:源端口号与Destination port目标端口号&#xff1a; 在TCP&#xff08;传输控制协议&#xff09;协议中&#xff0c;源端口和目标端口字段用于标识通信会话的端点。 &#xff08;1&#xff09;源端口是一个16位字段…

LeetCode 1206. 实现跳表

不使用任何库函数&#xff0c;设计一个跳表。 跳表是在 O(log(n)) 时间内完成增加、删除、搜索操作的数据结构。跳表相比于树堆与红黑树&#xff0c;其功能与性能相当&#xff0c;并且跳表的代码长度相较下更短&#xff0c;其设计思想与链表相似。 例如&#xff0c;一个跳表包…

3.数据查询(实验报告)

目录 一﹑实验目的 二﹑实验平台 三﹑实验内容和步骤 四﹑命令(代码)清单 五﹑调试和测试清单 一﹑实验目的 掌握使用Transact-SQL的SELECT语句进行基本查询的方法&#xff1b;掌握使用SELECT语句进行条件查询的方法&#xff1b;掌握SELECT语句的GROUP BY、ORDER BY以及UN…

MX6U根文件系统配置

编译 BusyBox 构建根文件系统 /home/ /linux/nfs mkdir rootfs tar -vxjf busybox-1.29.0.tar.bz2 依照自己的交叉编译 不然会出错 配置好 busybox 以后就可以编译了&#xff0c;我们可以指定编译结果的存放目录&#xff0c;我们肯定要将编 译结果存放到前面创建的 rootfs 目录…

(leetcode)66. 加一 67. 二进制求和(详解)

目录 66. 加一 思路 代码 67. 二进制求和 思路 代码 66. 加一 给定一个由 整数 组成的 非空 数组所表示的非负整数&#xff0c;在该数的基础上加一。 最高位数字存放在数组的首位&#xff0c; 数组中每个元素只存储单个数字。 你可以假设除了整数 0 之外&#xff0c;这…

7个最好的WordPress LMS在线学习管理系统比较

您是否正在为您的 WordPress 网站寻找最好的 LMS 在线学习管理系统插件&#xff1f; 学习管理系统 (LMS) 插件允许您创建和运行类似 Udemy 和 LearnDesk 等在线课程。一个完美的 WordPress LMS 插件拥有您管理在线课程、运行和评分测验、接受付款等所需的一切。 在本文中&…

【MySql】数据库 select 进阶

数据库 数据库表的设计ER 关系图三大范式 聚合函数与分组查询聚合函数 (count、sum、avg、max、min)分组查询 group by fields....having....(条件) 多表联查内连接外连接&#xff08;左连接&#xff0c;右连接&#xff09;自连接子查询合并查询 UNION 数据库表的设计 ER 关系…