一、算法伪码
allowance_greedy_distribution(coins, c):
// coins记录了硬币数量和面值,c是每天至少发放的津贴
将coins按照coins.value从大到小排序
day=0
while true:
today=0
for coin in coins:
尽可能选择面值大的硬币但不使其溢出
if 还有硬币:
使用还有的最小一种硬币,并使其数量-1
if 没有硬币:
break
二、引理
在硬币数目不受限制的情况下,
引理 P n P_n Pn:一组货币{ B 1 B_1 B1, B 2 B_2 B2,…, B n B_{n} Bn}满足整除性质,具有价值 W n = ∑ i = 1 n k i B i W_n=\sum_{i=1}^{n}{k_iB_i} Wn=i=1∑nkiBi,可以硬币数不增的将{ B 1 B_1 B1, B 2 B_2 B2,…, B n − 1 B_{n-1} Bn−1}尽数替换为 B n B_n Bn,并且这个过程结束后,满足 W n − 1 ′ < B n W_{n-1}^{'}<B_n Wn−1′<Bn详细证明请见2023-12-23 硬币选择问题 硬币找零问题 poj3040 贪心选择性质 数学证明-CSDN博客的引理部分。
三、贪心替换引理
硬币数量受限的贪心性质:至少存在一个从大到小选择硬币且满足硬币剩余数量都大于0的替换
引理:在硬币数量受限制时,存在一个满足贪心性质的替换。
证明:采用构造解法证明。
根据题意,至少有一组不一定满足贪心性质的解{
λ
1
,
λ
2
,
.
.
.
,
λ
n
\lambda _1,\lambda _2,...,\lambda _n
λ1,λ2,...,λn}使得
W
n
=
∑
i
=
1
n
λ
i
B
i
=
W
W_n=\sum_{i=1}^n\lambda_iB_i=W
Wn=i=1∑nλiBi=W
先假设硬币数量不受限制,由
P
n
P_n
Pn,一定有一组满足贪心性质的替换{
λ
1
′
,
λ
2
′
,
.
.
.
,
λ
n
′
\lambda _{1}^{'},\lambda _{2}^{'},...,\lambda _{n}^{'}
λ1′,λ2′,...,λn′},且满足
λ
i
<
=
λ
i
′
,
i
>
=
2
\lambda_i<=\lambda_{i}^{'},i>=2
λi<=λi′,i>=2以及
λ
i
<
=
c
o
i
n
s
[
i
]
.
n
u
m
b
e
r
s
,
i
>
=
1
\lambda_i<=coins[i].numbers,i>=1
λi<=coins[i].numbers,i>=1
再按照以下方法构造一个满足贪心性质的替换:
从
λ
n
′
到
λ
2
′
\lambda _{n}^{'} 到\lambda _{2}^{'}
λn′到λ2′依次进行如下操作:
{
将超出数额的硬币换成若干个面值次大的硬币
λ
k
>
c
o
i
n
[
k
]
.
n
u
m
b
e
r
s
c
o
n
t
i
n
u
e
λ
k
<
=
c
o
i
n
[
k
]
.
n
u
m
b
e
r
s
\left\{\begin{matrix} 将超出数额的硬币换成若干个面值次大的硬币 & \lambda_k >coin[k].numbers \\ continue & \lambda_k<=coin[k].numbers \\ \end{matrix}\right.
{将超出数额的硬币换成若干个面值次大的硬币continueλk>coin[k].numbersλk<=coin[k].numbers
利用这个方法得到一组除了
λ
1
′
′
\lambda _{1}^{''}
λ1′′外一定满足贪心性质且合法的替换{
λ
1
′
′
,
λ
2
′
′
,
.
.
.
,
λ
n
′
′
\lambda _{1}^{''},\lambda _{2}^{''},...,\lambda _{n}^{''}
λ1′′,λ2′′,...,λn′′}。
因为
λ
k
′
′
\lambda _{k}^{''}
λk′′(k>=2)不是coins[k].numbers就是
λ
k
′
\lambda _{k}^{'}
λk′,所以一定有
λ
i
<
=
λ
i
′
′
,
i
>
=
2
\lambda_i<=\lambda_{i}^{''},i>=2
λi<=λi′′,i>=2
下面利用反证法证明其是一组合法替换。
假设这组替换不合法,则有这组替换
W
n
′
′
=
∑
i
=
2
n
λ
i
′
′
B
i
+
λ
1
′
′
B
1
>
=
∑
i
=
2
n
λ
i
B
i
+
λ
1
′
′
B
1
>
∑
i
=
1
n
λ
i
B
i
=
W
W_{n}^{''}=\sum_{i=2}^{n}\lambda_{i}^{''}B_i+\lambda_{1}^{''}B_1>=\sum_{i=2}^n\lambda_iB_i+\lambda_{1}^{''}B_1>\sum_{i=1}^n\lambda_iB_i=W
Wn′′=i=2∑nλi′′Bi+λ1′′B1>=i=2∑nλiBi+λ1′′B1>i=1∑nλiBi=W,这与构造过程的W不变相矛盾,故这个替换一定是一组合法替换。
四、贪心选择性质
该Allowence_distribution问题的贪心选择性质为:至少包含一个每次尽可能少给牛但不低于c,且每次选尽可能大但是工资不溢出的构造得到的最优解,
假设 D i D_i Di为第i天的一个分配,其价值为 D i . v a l u e D_i.value Di.value,其分配为 D i . λ [ ] D_i.\lambda[] Di.λ[]。
第一步:
假设有一个解是最优解,但是不满足上述贪心选择性质,由于
D
[
]
D[]
D[]与其天数顺序无关,可以进行排序得到它的一个同构解,并且按照这个顺序进行构造时,每一步的多余工资
D
i
.
c
o
s
t
=
D
i
.
v
a
l
u
e
−
c
>
=
D
贪心
i
.
c
o
s
t
D_i.cost=D_i.value-c>=D_{贪心i}.cost
Di.cost=Di.value−c>=D贪心i.cost
倘若解
D
贪心
[
]
D_{贪心}[]
D贪心[]能够发n天工资,则有总价值
W
=
n
c
+
∑
i
=
1
n
D
贪心
i
.
c
o
s
t
+
D
贪心
.
l
a
s
t
=
n
c
+
∑
i
=
1
n
D
i
.
c
o
s
t
+
D
.
l
a
s
t
W=nc+\sum_{i=1}^{n}D_{贪心i}.cost+D_{贪心}.last=nc+\sum_{i=1}^{n}D_{i}.cost+D.last
W=nc+i=1∑nD贪心i.cost+D贪心.last=nc+i=1∑nDi.cost+D.last
则对于每个n都有:
D
贪心
.
l
a
s
t
>
=
D
.
l
a
s
t
D_{贪心}.last>=D.last
D贪心.last>=D.last
故只要
D
.
l
a
s
t
D.last
D.last能够一天工资,
D
贪心
.
l
a
s
t
D_{贪心}.last
D贪心.last就至少够发一天工资。
第二步:
则假设有一个最优解
D
i
D_i
Di,由贪心替换引理,可以得到一个最优解的同构
D
i
′
D_{i}^{'}
Di′。
证毕。
五、代码实现
可以直接运行的示例:
#include <iostream>
#include <vector>
#include <queue>
#include <list>
#include <math.h>
//#include <assert.h>
using namespace std;
//using Datatype = int;
typedef long long Datatype;
typedef long long Elemtype;
struct COIN {
Datatype value;
Elemtype numbers;
};
Elemtype allowence_distribution_greedy(list<COIN>& coins, Datatype c); // 接受硬币的数据和每天的津贴数,返回够发的天数
int main() {
list<COIN> coins = { {10,1},{1,100},{5,120} };
cout << allowence_distribution_greedy(coins, 6);
}
Elemtype allowence_distribution_greedy(list<COIN>& coins, Datatype c) {
coins.sort([](COIN a, COIN b) { return a.value > b.value; });
Elemtype days = 0;
while (1) {
Datatype today = 0;
auto it = coins.begin();
while (it != coins.end())
{
if (c == today)break;
if (c < today + it->value)it++;
else {
today += it->value;
it->numbers--;
if (0 == it->numbers)coins.erase(it++);
}
}
if (c == today)days++;
else if (!coins.empty()) {
// 只要还有硬币,必然再加一块硬币就超过c
days++;
auto it = --coins.end();
it->numbers--;
//assert(today + it->value > c);
if (0 == it->numbers)coins.erase(it);
}
if (coins.empty())break;
}
return days;
}
六、补充
补充部分和本文无任何干系,仅供看个乐子。
- 本题使用了两次贪心选择性质。
- 贪心选择性一般而言,都出现在顺序无关的情况下。