图论 - 教科书般的亵渎 - from jisuanke


环境里有 n 个怪物,他们的生命值用一个正整数表示。现在,你可以使用两种魔法,对怪物进行攻击。当怪物的生命值小于等于 0 时,他便被消灭了。

  1. 魔法箭,对摸个生物造成 k 点伤害,对一个生物最多使用一次,但没有使用次数限制。

  2. 亵渎,对所有生物造成一点伤害,如果杀死了某个生物,则继续自动重新使用该法术。只能主动使用一次,且必须最后使用。

请问,最多能消灭多少个怪物?亵渎法术最多能释放几次?

输入格式

第一行两个整数 n 和 k ,表示怪物的数量和法术的伤害。第二行 n 个正整数,依次表示每个怪物的生命值。

输出格式

一行,两个整数,表示最多能消灭多少怪物和亵渎法术最多被释放的次数。

数据范围

对于 40% 的数据 n200 。

对于全部数据, n,k100000, 怪物的生命上限为 100000

样例输入
5 1
1 2 3 5 7
样例输出
4 5

建图: 把血量看作点,对于 ai > k,连接 ai 和 ai - k

这样,图将会分成若干个联通块,分别考察每一个联通块:

    1:这个联通块具有不少于节点数量的边数,则这些节点代表血量都可以取到;

    2:这个联通是树,还有两种情况:

             (1):联通块中的点,在给定序列中出现次数超过联通块中边数(这种情况是由于此联通块中最小点无法向更低血量连边而出现的),这些节点代表的血量同样都可以取到;

             (2): otherwise,序列出现次数 = 边数,这个联通块有一个点会无法取到,简单的贪心策略,不取块中的最大血量。


综合以上的判断方式,我们可以得知最佳处理下会得到哪些血量,进而求得了亵渎次数。

同时,可以证明,采取这种操作可以杀死最多的怪物。


Code

#include <iostream>
#include <vector>

using namespace std;

#define REP(i, a, b) for (int i = (a), i##_end_ = (b); i < i##_end_; ++i)
#define debug(...) fprintf(stderr, __VA_ARGS__)
#define mp make_pair
#define x first
#define y second
#define pb push_back
#define SZ(x) (int((x).size()))
#define ALL(x) (x).begin(), (x).end()

#define max(a, b) (a > b ? a : b)
#define min(a, b) (a < b ? a : b)

typedef long long LL;

const int N = 120000;
int n, k, cnt, ans1, ans2;
int a[N], vis[N], vtx[N], egs[N], mxn[N];
bool tree[N], ok[N];
vector< vector<int> > edges(N);

void dfs(int cur, int fa, int tag) {
	vis[cur] = tag;
	REP(i, 0, edges[cur].size()) {
		int vertex = edges[cur][i];
		if (vis[vertex] && vertex != fa) tree[tag] = false;
		if (!vis[vertex]) dfs(vertex, cur, tag);
	}
}

int main() {
	memset(tree, 1, sizeof tree);
	scanf("%d%d", &n, &k);
	REP(i, 1, n + 1) scanf("%d", &a[i]);
	REP(i, 1, n + 1)
		if (a[i] > k)
			edges[a[i]].pb(a[i] - k),
			edges[a[i] - k].pb(a[i]);
	REP(i, 1, N) if (!vis[i]) dfs(i, 0, ++cnt);
	REP(i, 1, n + 1) vtx[vis[a[i]]]++;
	REP(i, 1, n + 1) if (a[i] > k) egs[vis[a[i]]]++;
	REP(i, 1, N) mxn[vis[i]] = max(mxn[vis[i]], i);
	REP(i, 1, N)
		if (vtx[vis[i]] > egs[vis[i]]) ok[i] = true;
		else if (tree[vis[i]] && i == mxn[vis[i]]) ok[i] = false;
		else ok[i] = true;
	REP(i, 1, N) if (ok[i] == false) { ans2 = i - 1; break; }
	REP(i, 1, n + 1) if (a[i] - k <= ans2) ans1++;
	cout << ans1 << " " << ans2 + 1 << endl;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值