Johnson法则 BZOJ 3709 Bohater、洛谷 P1080 国王游戏、ZOJ3689 Digging

Johnson 符合某种排序规律的贪心

Johnson法则的题目往往可以通过排序后,得到贪心顺序
排序的依据往往可以通过比较先a后b和先b后a两种情况得到

BZOJ 3709 Bohater

题意

在一款电脑游戏中,你需要打败n只怪物(从1到n编号)。为了打败第i只怪物,你需要消耗d[i]点生命值,但怪物死后会掉落血药,使你恢复a[i]点生命值。任何时候你的生命值都不能降到0(或0以下)。请问是否存在一种打怪顺序,使得你可以打完这n只怪物而不死掉

分析

令hp为当前生命值,d1打1号怪所需血量,d2打2号怪所需血量,a1为打1号怪所回血量,a2为打2号怪所回血量.计算打完怪后的状态(血量最低的时候)

先打1再打2: m i n ( h p − d 1 , h p − d 1 + a 1 − d 2 ) min(hp-d_{1},hp-d_{1}+a_{1}-d_{2}) min(hpd1,hpd1+a1d2)
先打2再打1: m i n ( h p − d 2 , h p − d 2 + a 2 − d 1 ) min(hp-d_{2},hp-d_{2}+a_{2}-d_{1}) min(hpd2,hpd2+a2d1)

令先打1的情况更优:
m i n ( h p − d 1 , h p − d 1 + a 1 − d 2 ) > m i n ( h p − d 2 , h p − d 2 + a 2 − d 1 ) min(hp-d_{1},hp-d_{1}+a_{1}-d_{2})>min(hp-d_{2},hp-d_{2}+a_{2}-d_{1}) min(hpd1,hpd1+a1d2)>min(hpd2,hpd2+a2d1)
m i n ( − d 1 , − d 1 + a 1 − d 2 ) > m i n ( − d 2 , − d 2 + a 2 − d 1 ) min(-d_{1},-d_{1}+a_{1}-d_{2})>min(-d_{2},-d_{2}+a_{2}-d_{1}) min(d1,d1+a1d2)>min(d2,d2+a2d1)

以以上依据排序贪心即可

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#pragma warning (disable:4996)
typedef long long LL;
const int maxn = 100005;
struct Node
{
	int d;
	int a;
	int id;
}node[maxn];
bool cmp(const Node& a,const Node& b) {		//排序依据
	return min(-a.d, -a.d + a.a - b.d) > min(-b.d, -b.d + b.a - a.d);
}
int main() {
	int n, z;
	scanf("%d%d", &n, &z);
	for (int i = 1; i <= n; i++) {
		scanf("%d%d", &node[i].d, &node[i].a);
		node[i].id = i;
	}
	sort(node + 1, node + 1 + n, cmp);
	LL ans = z; int i;
	for (i = 1; i <= n; i++) {
		ans -= node[i].d;
		if (ans <= 0)break;
		ans += node[i].a;
	}
	if (i > n) {
		printf("TAK\n");
		for (int i = 1; i <= n; i++) {
			printf("%d ", node[i].id);
		}
		putchar('\n');
	}
	else printf("NIE\n");

}

洛谷 P1080 国王游戏

题意

国王和n个大臣,每人左手有一个正整数,右手上有一个正整数。

第i个大臣拿到的钱为排在该大臣前面的所有人的左手数的乘积除以他自己右手上的数,然后向下取整。

国王希望通过给大臣排队的方式最小化最大值。国王必须站在队伍最前面。

分析

令p为之前所有人左手上的乘积,a1为大臣1左手的数,a2为大臣2左手上的数,b1为大臣1右手上的数,b2为大臣2右手上的数

先大臣1后大臣2: m a x ( p b 1 , p ∗ a 1 b 2 ) max(\frac{p}{b1},\frac{p*a1}{b2}) max(b1p,b2pa1)
先大臣2后大臣1: m a x ( p b 2 , p ∗ a 2 b 1 ) max(\frac{p}{b2},\frac{p*a2}{b1}) max(b2p,b1pa2)

令先大臣1所取得的最大值更小:
m a x ( p b 1 , p ∗ a 1 b 2 ) &lt; m a x ( p b 2 , p ∗ a 2 b 1 ) max(\frac{p}{b1},\frac{p*a1}{b2})&lt;max(\frac{p}{b2},\frac{p*a2}{b1}) max(b1p,b2pa1)<max(b2p,b1pa2)

因为显然 p ∗ a 2 b 1 &gt; p b 1 \frac{p*a2}{b1}&gt;\frac{p}{b1} b1pa2>b1p, p ∗ a 1 b 2 &gt; p b 2 \frac{p*a1}{b2}&gt;\frac{p}{b2} b2pa1>b2p
原式即为: p ∗ a 1 b 2 &lt; p ∗ a 2 b 1 \frac{p*a1}{b2}&lt;\frac{p*a2}{b1} b2pa1<b1pa2
判断结果为: a 1 ∗ b 1 &lt; a 2 ∗ b 2 a1*b1&lt;a2*b2 a1b1<a2b2

以以上依据排序即可,注意高精度

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#pragma warning(disable:4996)
struct minst {
	int l;
	int r;
	int s;
} m[1100];
int res[4100], ans[4100], add[4100];
bool cmp(minst a, minst b) {
	return a.s < b.s;
}
void mul(int x) {
	for (int i = 0; i <= ans[0] + 5; ++i)
		add[i] = 0;
	for (int i = 1; i <= ans[0]; i++) {
		ans[i] *= x;
		add[i + 1] += ans[i] / 10;
		ans[i] %= 10;
	}
	for (int i = 1; i <= ans[0] + 4; i++) {
		ans[i] += add[i];
		ans[i + 1] += ans[i] / 10;
		ans[i] %= 10;
		if (ans[i])
			ans[0] = max(ans[0], i);
	}
}
void div(int x) {
	for (int i = 0; i <= ans[0]; i++)
		add[i] = 0;
	int q = 0;
	for (int i = ans[0]; i >= 1; i--) {
		q = q * 10 + ans[i];
		if (q >= x) {
			add[i] = q / x;
			q %= x;
			if (!add[0])
				add[0] = i;
		}
	}
}
void compare() {
	if (add[0] > res[0]) {
		for (int i = add[0]; i >= 0; i--)
			res[i] = add[i];
	} else if (add[0] == res[0]) {
		for (int i = add[0]; i >= 1; i--) {
			if (add[i] < res[i])
				return;
			else if (add[i] > res[i]) {
				for (int j = i; j >= 0; j--)
					res[j] = add[j];
				return;
			}
		}
	}
}
int main() {
	int n;
	scanf("%d", &n);
	for (int i = 0; i <= n; i++) {
		scanf("%d%d", &m[i].l, &m[i].r);
		m[i].s = m[i].l*m[i].r;
	}
	sort(m + 1, m + 1 + n, cmp);
	ans[0] = ans[1] = 1;
	for (int i = 1; i <= n; i++) {
		mul(m[i - 1].l);
		div(m[i].r);
		compare();
	}
	for (int i = res[0]; i >= 1; i--)
		printf("%d", res[i]);
	putchar('\n');
}

ZOJ3689 Digging

题意

距离世界末日还有T天,你想要建立一些奇迹,发末日财。
第i个奇迹需要ti天,第i个奇迹的体积是si。
距末日t天开始建立,ti天后建完,获得金钱si * t
自定义建立的奇迹集合和建立顺序,使得赚的钱最多。

分析

本题与上题不同的是有时间限制,在有限的时间里使建立的奇迹价值尽可能大
01背包问题很像。但是选择建立哪些奇迹,需要甄别

令离末日还有p天,s1为奇迹1的体积,s2为奇迹2的体积,t1为奇迹1的建造天数,t2为奇迹2的建造天数

先奇迹1再奇迹2: p ∗ s 1 + ( p − t 1 ) ∗ s 2 p*s1+(p-t1)*s2 ps1+(pt1)s2
先奇迹2再奇迹1: p ∗ s 2 + ( p − t 2 ) ∗ s 1 p*s2+(p-t2)*s1 ps2+(pt2)s1

令先奇迹1价值大: p ∗ s 1 + ( p − t 1 ) ∗ s 2 &gt; p ∗ s 2 + ( p − t 2 ) ∗ s 1 p*s1+(p-t1)*s2&gt;p*s2+(p-t2)*s1 ps1+(pt1)s2>ps2+(pt2)s1
最终得: s 1 ∗ t 2 &gt; t 1 ∗ s 2 s1*t2&gt;t1*s2 s1t2>t1s2

综上先贪心排序再01背包dp即可

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#pragma warning (disable:4996)
typedef long long LL;
const int maxn = 3500;
struct Node
{
	int s;
	int t;
	double ops;
}node[maxn];
bool cmp(const Node& a, const Node& b) {
	return a.ops > b.ops;
}
LL dp[10005];
int main() {
	int n, T;
	while (~scanf("%d%d", &n, &T)) {
		for (int i = 1; i <= n; i++) {
			scanf("%d%d", &node[i].t, &node[i].s);
			node[i].ops = node[i].s * 1.0 / node[i].t;
		}
		sort(node + 1, node + 1 + n, cmp);
		for (int i = 1; i <= n; i++) {
			for (int j = T - node[i].t; j >= 0; j--)
				dp[j + node[i].t] = max(dp[j + node[i].t],
					dp[j] + node[i].s * (LL(T) - j));
		}
		LL Max = 0;
		for (int i = 0; i <= T; i++)
			Max = max(Max, dp[i]);
		printf("%lld\n", Max);
		fill(dp, dp + 1 + T, 0);
	}
}

严格弱序

注意为实现Johnson法则排序,排序规则必须符合严格弱序

即非自反性,非对称性和传递性(离散数学)

x≮x(非自反性)
若 x<y,则 y≮x(非对称性)
若 x<y,y<z,则 x<z(传递性)
若 x≮y,y≮x,y≮z,z≮y,则 x≮z,z≮x (不可比性的传递性)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值