【题解】Luogu-P4774 [NOI2018] 屠龙勇士

与恶龙缠斗过久,自身亦成为恶龙。凝视深渊过久,深渊将回以凝视。

​ ——尼采《善恶的彼岸》

P4774 [NOI2018] 屠龙勇士

\(\text{Description}\)

玩家需要按照编号 \(1 \to n\) 顺序杀掉 \(n\) 条巨龙,每条巨龙拥有一个初始的生命值 \(b_i\)。同时每条巨龙拥有恢复能力,会使它的生命值每次增加 \(a_i\),直至生命值非负。

游戏开始时玩家拥有 \(m\) 把剑,每把剑拥有一个攻击力 \(atk_i\),每次面对巨龙时,玩家只能选择一把剑,当杀死巨龙后这把剑就会消失,但玩家会获得全新的一把剑。

每次面对巨龙时,玩家会选择攻击力不高于巨龙初始生命值中攻击力最大的一把剑作为武器。如果没有这样的剑,则选择 攻击力最低 的一把剑作为武器。

面对每条巨龙,玩家都会使用选择的剑攻击巨龙 \(x\) 次(\(x\) 为常数),使巨龙的生命值减少 \(x \cdot atk\)。

之后,巨龙会不断使用恢复能力,每次恢复 \(a_i\) 生命值。若在恢复前或某一次恢复后其生命值为 \(0\),则巨龙死亡,玩家通关。

现在给出了每条巨龙的所有属性,请求出应该将机器人的攻击次数 \(x\) 设置为多少,才能用最少的攻击次数通关游戏。

如果无论设置成多少都无法通关游戏,输出 \(-1\)。

\(\text{Solution}\)

攻击力不高于巨龙初始生命值中攻击力最大的一把剑作为武器

直接用平衡树或 \(\rm multiset\) 实现即可,我们设这把剑的攻击力为 \(coe_i\)。

那么要使生命值为 \(0\),其实就是求最小的非负整数 \(x\),使得

\[\begin{cases} coe_1\cdot x\equiv b_1\pmod {a_1}\\ coe_2\cdot x\equiv b_2\pmod {a_2}\\ \cdots\\ coe_n\cdot x\equiv b_n\pmod {a_n} \end{cases} \]

对于这样一个同余方程 \(coe\cdot x\equiv b\pmod a\),直接用 \(\rm exgcd\) 将其转化成 \(x\equiv b'\pmod{\frac{a}{\gcd(coe,a)}}\) 即可。

注意模数一定要除以 \(\gcd(coe,a)\),不然会漏解。(这个小奥应该都讲了)

接着就是一个 \(\rm exCRT\) 了。

注意事项:每条龙的血量必须是负数才会开始回血,所以在处理的时候用一个变量 \(mn\) 记录至少要攻击多少下,\(mn=\max\left\{\left\lfloor\dfrac{b_i}{atk_i}\right\rfloor\right\}\),然后将解出来的 \(x\) 加上一个 \(\operatorname{lcm}(a_1,a_2,\dots,a_n)\) 的倍数使得 \(x\ge mn\) 即可。

就是这样一个东西让我调了 \(5\) 天并让做这道题的 \(6\) 个人全部怀疑人生

\(\text{Code}\)

//18 = 9 + 9 = 18.
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <cstring>
#define Debug(x) cout << #x << "=" << x << endl
typedef long long ll;
using namespace std;

const int MAXN = 1e5 + 5;

ll a[MAXN], b[MAXN], coe[MAXN];

namespace Math
{
	ll div_ceil(ll x, ll y)
	{
		return (x - 1) / y + 1;
	}
	
	ll x, y;
	
	ll exgcd(ll a, ll b)
	{
		if (!b)
		{
			x = 1, y = 0;
			return a;
		}
		ll Gcd = exgcd(b, a % b);
		ll tmp = x;
		x = y;
		y = tmp - a / b * y;
		return Gcd;
	}
	
	ll qmul(ll a, ll b, ll p)
	{
		if (b < 0)
		{
			a = -a;
			b = -b;
		}
		ll base = a, ans = 0;
		while (b)
		{
			if (b & 1)
			{
				ans = (ans + base) % p;
			}
			base = (base + base) % p;
			b >>= 1;
		}
		return (ans + p) % p;
	}
	
	int pre(int n)
	{
		for (int i = 1; i <= n; i++)
		{
			ll Gcd = exgcd(coe[i], a[i]);
			if (b[i] % Gcd)
			{
				return -1;
			}
			a[i] /= Gcd; // 一定要先除模数
			b[i] /= Gcd;
			b[i] = (qmul(x, b[i], a[i]) + a[i]) % a[i]; // 再取模
		}
		return 1;
	}
	
	ll A, B;
	
	int merge(ll a, ll b)
	{
		ll Gcd = exgcd(A, a), c = B - b;
		if (c % Gcd)
		{
			return -1;
		}
		c /= Gcd;
		x = (qmul(x, c, a) + a) % a;
		ll Lcm = A / Gcd * a;
		B = (B - qmul(A, x, Lcm) + Lcm) % Lcm;
		A = Lcm;
		return 1;
	}
	
	ll exCRT(int n, ll mn)
	{
		if (pre(n) == -1)
		{
			return -1;
		}
		A = a[1], B = b[1];
		for (int i = 2; i <= n; i++)
		{
			if (merge(a[i], b[i]) == -1)
			{
				return -1;
			}
		}
		if (B < mn)
		{
			B += div_ceil(mn - B, A) * A;
		}
		return B;
	}
}
using Math::div_ceil;
using Math::exCRT;

namespace BST
{
	int tot, rt;
	
	struct treap
	{
		int lson, rson;
		ll val;
		int key;
	}t[MAXN << 1];
	
	void init()
	{
		tot = rt = 0;
		memset(t, 0, sizeof(t));
	}
	
	int build(ll val)
	{
		t[++tot] = treap{0, 0, val, rand()};
		return tot;
	}
	
	void split(int pos, ll val, int &a, int &b)
	{
		if (!pos)
		{
			a = b = 0;
			return;
		}
		if (t[pos].val <= val)
		{
			a = pos;
			split(t[pos].rson, val, t[a].rson, b);
		}
		else
		{
			b = pos;
			split(t[pos].lson, val, a, t[b].lson);
		}
	}
	
	int merge(int a, int b)
	{
		if (!a || !b)
		{
			return a + b;
		}
		if (t[a].key > t[b].key)
		{
			t[a].rson = merge(t[a].rson, b);
			return a;
		}
		else
		{
			t[b].lson = merge(a, t[b].lson);
			return b;
		}
	}
	
	void insert(ll val)
	{
		int a = 0, b = 0, c = build(val);
		split(rt, val, a, b);
		rt = merge(merge(a, c), b);
	}
	
	void remove(ll val)
	{
		int a = 0, b = 0, c = 0;
		split(rt, val, a, b);
		split(a, val - 1, a, c);
		c = merge(t[c].lson, t[c].rson);
		rt = merge(merge(a, c), b);
	}
	
	ll getpre(ll val) // 小于等于 val 的最大数
	{
		int a = 0, b = 0;
		split(rt, val, a, b);
		int pos = a;
		while (t[pos].rson)
		{
			pos = t[pos].rson;
		}
		rt = merge(a, b);
		return t[pos].val;
	}
	
	ll getmin()
	{
		int pos = rt;
		while (t[pos].lson)
		{
			pos = t[pos].lson;
		}
		return t[pos].val;
	}
}
using BST::init;
using BST::insert;
using BST::remove;
using BST::getpre;
using BST::getmin;

int n, m;
ll mn;
ll atk[MAXN], rew[MAXN];

void read()
{
	for (int i = 1; i <= n; i++)
	{
		scanf("%lld", b + i);
	}
	for (int i = 1; i <= n; i++)
	{
		scanf("%lld", a + i);
	}
	for (int i = 1; i <= n; i++)
	{
		scanf("%lld", rew + i);
	}
	for (int i = 1; i <= m; i++)
	{
		scanf("%lld", atk + i);
	}
}

void cal()
{
	init();
	for (int i = 1; i <= m; i++)
	{
		insert(atk[i]);
	}
	mn = 0;
	for (int i = 1; i <= n; i++)
	{
		ll res = getpre(b[i]);
		if (res)
		{
			coe[i] = res;
		}
		else
		{
			coe[i] = getmin();
		}
		mn = max(mn, div_ceil(b[i], coe[i]));
		remove(coe[i]);
		insert(rew[i]);
	}
}

int main()
{
//	freopen("dragon.in", "r", stdin);
//	freopen("dragon.out", "w", stdout);
	srand((unsigned)time(NULL));
	int t;
	scanf("%d", &t);
	while (t--)
	{
		scanf("%d%d", &n, &m);
		read();
		cal();
		printf("%lld\n", exCRT(n, mn));
	}
	return 0;
}

与《屠龙勇士》缠斗过久,自身亦成为屠龙勇士,也就是那只恶龙。

上一篇:CCPC网络选拔赛Round2 Monopoly 题解


下一篇:E - CCPC Strings HDU - 6942 数列求和+逆元+容斥