【堆】【并查集】【牛客模拟赛】牛半仙的魔塔

链接

D − 牛 半 仙 的 魔 塔 D-牛半仙的魔塔 D

题目描述

牛半仙的妹子被大魔王抓走了,牛半仙为了就他的妹子,前往攻打魔塔。
魔塔为一棵树,牛半仙初始在一号点。
牛半仙有攻击,防御,血量三个属性。
除一号点外每个点都有魔物防守,魔物也有攻击,防御,血量三个属性。
每个怪物后面都守着一些蓝宝石,获得1蓝宝石可增加1防。
牛半仙具有突袭属性,所以遇到魔物后会率先发动攻击,然后牛半仙和魔物轮换地攻击对方。
一个角色被攻击一次减少的血量是对方的攻击减去自己的防御。
当一个角色的血量小于等于 0 时,他就会死亡。
当牛半仙第一次到达某个节点时会与这个节点的魔物发生战斗。
当一个魔物死亡后,这个魔物所在的节点就不会再产生新的魔物。
现在牛半仙想知道他打死魔塔的所有魔物后的最大血量。

输入

第一行一个 n 代表节点数。
随后 n-1 行,每行两个数 i,j,表示 i 与 j 节点有边相连。
随后一行,三个数,依次为勇士的血量、攻击、防御。
随后 n-1 行,每行四个数,依次为怪物的血量、攻击、防御,和其守着的蓝宝石数量。

输出

一个数,代表最大血量。如果牛半仙在打死魔塔的所有魔物之前就已经死亡了,则输出 -1。

样例输入

6
1 2
1 3
1 4
4 5
5 6
50000 10 0
35 54 2 4
25 55 3 5
21 51 4 5
20 64 5 3
43 64 6 1

样例输出

48901

数据范围或提示

打怪的顺序依次为4,3,5,2,6
可以证明不存在更优的方案。
对于20%的数据:n≤15
又有30%的数据:n≤1000,且保证对于每一条边 (i,j),一定满足 i=1
对于前90%的数据:n≤1000,且保证对于每一条边(i,j),一定满足 j=i+1 或 i=1
对于最后10%的数据:n≤100000,且保证对于每一条边(i,j),一定满足 j=i+1 或 i=1 
对于100%的数据:有牛半仙血量<5*10^18,攻击=2000,盔甲防御=0。
								怪物血量为3000~10^6 攻击5 * 10^5- 7 * 10^5
								防御≤1000,打完一只怪后获得的蓝宝石数量为1至5

思路

因为牛半仙攻击一定,那么打一只怪的次数是不变的
那么我们考虑攻打点的先后顺序
设性价比为 打 这 只 怪 获 得 的 蓝 宝 石 数 量 攻 打 一 只 怪 所 需 次 数 \frac{打这只怪获得的蓝宝石数量}{攻打一只怪所需次数}
然后我们用一个并查集,如果当前点的父亲节点性价比比它儿子小,就并成一个点
然后我们从1号点出发,每次将可以到达的点加入大根堆维护就好了
考场上没想到这种做法, 只想到了性价比,但是没有想到大根堆还有并查集,痛失不知道多少分,血亏

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#define ll long long

using namespace std;


priority_queue < pair < double, int > > Q;
int n, h[100001], fa[100001], fath[100001], X, t;
ll Hp[100001], Ad[100001], Def[100001], Num[100001], tim[100001];
bool vis[100001];

struct node {
    int to, next;
}w[200001];

void add(int x, int y)
{
	w[++t] = (node){y, h[x]}; h[x] = t;
	w[++t] = (node){x, h[y]}; h[y] = t;
}

void dfs(int now, int fath) {
    for (int i = h[now]; i; i = w[i].next)
        if (w[i].to != fath) {
            fa[w[i].to] = now;
            dfs(w[i].to, now);
        }
}
 
int find(int now) {
    if (now == fath[now]) return now;
    return fath[now] = find(fath[now]);
}//寻找钡钡【误

int main()
{
	scanf("%d", &n);
	for (int i = 1; i < n; ++i)
	{
		int x, y;
		scanf("%d%d", &x, &y);
		add(x, y);
	}
	fath[1] = 1;
	scanf("%lld%lld%lld", &Hp[1], &Ad[1], &Def[1]);//牛半仙的血量攻击防御
	for (int i = 2; i <= n; ++i)
	{
		fath[i] = i;
		scanf("%lld%lld%lld%lld", &Hp[i], &Ad[i], &Def[i], &Num[i]);
		tim[i] = Hp[i] / (Ad[1] - Def[i]);//计算出要多少次攻击
		if (Hp[i] % (Ad[1] - Def[i]) == 0) tim[i]--;
		Hp[1] -= (Ad[i] - Def[1]) * tim[i];
		Q.push(make_pair(1.0 * Num[i] / tim[i], i)); 
	}
	dfs(1, 0);
    vis[1] = 1;
    while (!Q.empty()) {
        int now = Q.top().second;
        Q.pop();
        if (vis[now]) continue;
        vis[now] = 1;
        X = find(fa[now]);
        Hp[1] += tim[now] * Num[X];//补回前面没算防御扣的血
        tim[X] += tim[now];
        Num[X] += Num[now];
        if (!vis[X]) Q.push(make_pair(1.0 * Num[X] / tim[X], X));
        fath[now] = X;
    }
    printf("%lld", Hp[1]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值