loj 2721 [NOI2018] 屠龍勇士
loj 2721 [NOI2018] 屠龍勇士
有 \(n\) 條惡龍,每條惡龍有一個初始生命值 \(a_i\) ,恢復力 \(p_i\) , 擊殺後會掉落一把劍.
初始你有 \(m\) 把劍.
給出每把劍的攻擊力.
現在你打算按照以下方法殺掉所有惡龍
- 選擇一個引數 \(x\)
- 從 \(1\) 到 \(n\) 開始殺龍,對於第 \(i\) 條龍
- 選擇攻擊力小於等於 \(a_i\) 的劍中攻擊力最高的那把,如果不存在則選擇所有劍裡攻擊力最小的一把,設其攻擊力為 \(ATK\)
- 用這把劍攻擊 \(x\) 次,然後這把劍消失,惡龍的生命值減少 \(ATK \cdot x\)
- 然後惡龍開始恢復,每次生命值增加 \(p_i\) ,如果某個時刻,惡龍的生命值為 \(0\) ,則擊殺成功
問最小的 \(x\) ,如果不存在能殺掉所有惡龍的 \(x\) ,輸出 \(-1\)
有 \(T\) 組資料
\(n \le 10^5, m \le 10^5, T \le 5, a_i \le 10^{12}\)
所有 \(p_i\) 的最小公倍數 \(\le 10^{12}\)
所有劍的攻擊力 \(\le 10^6\)
Tutorial
很容易用multiset在 \(O(n \log n)\) 的時間得到每條龍會使用哪一把劍.特判了 \(m=0\) 的情況
設當前劍的攻擊力為 \(b\) ,那麼 \(x\)
\[a_i - bx + kp_i = 0, k \ge 0 \]
其中 \(k \ge 0\) 的部分可以在最後強制 \(x \ge \lceil \dfrac {a_i} b \rceil\) 就好了.
現在可以寫作
\[bx + kp_i = a_i \]
這是一個一元二次方程,由於有 \(n\) 個這樣的方程需要合併,所以我們將它轉化為關於 \(x\) 的同餘方程的形式
具體步驟就是,設 \(d = \gcd(b, p_i)\) ,若 \(d \nmid a_i\) 則一元二次方程無解.否則將 \(b,p_i,a_i\) 同時除以 \(d\) .然後就可以轉化為
\[x \equiv a_i \cdot \dfrac 1b \mod p_i \]
其中 \(b\) 和 \(p_i\) 此時是互質的,用exgcd計算逆元即可.
然後將這 \(n\) 個方程用擴充套件中國剩餘定理合併.時間複雜度 \(O(n \log n)\)
由於 \(p_i\) 的最小公倍數是 \(10^{12}\) 級別的,所以需要快速乘.
總時間複雜度為 \(O(n \log n)\)
Code
#include <cstdio>
#include <iostream>
#include <set>
#define debug(...) fprintf(stderr,__VA_ARGS__)
using namespace std;
inline char nc()
{
static char buf[100000],*l=buf,*r=buf;
return l==r&&(r=(l=buf)+fread(buf,1,100000,stdin),l==r)?EOF:*l++;
}
template<class T> void read(T &x) {
x=0; int f=1,ch=nc();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=nc();}
while(ch>='0'&&ch<='9'){x=x*10-'0'+ch;ch=nc();}
x*=f;
}
template<class T> inline bool Cmax(T &x, T y) {return x < y ? x = y, 1 : 0;}
typedef long long ll;
typedef long double ld;
const int maxn = 1e5 + 50;
int T;
int n, m;
int c[maxn];
ll a[maxn], p[maxn];
multiset<ll> s;
inline ll mul(ll x, ll y, ll mod)
{
if(mod <= 1e9) return x * y % mod;
if(mod <= 1e12) return (((x * (y >> 20) % mod) << 20) + x * (y & ((1 << 20) - 1))) % mod;
ll re = x * y - (ll)((ld)x * y / mod + 0.5) * mod;
if(re < 0) re += mod;
return re;
}
ll gcd(ll a, ll b) {return b == 0 ? a : gcd(b, a % b);}
ll exgcd(ll a, ll b, ll &x, ll &y)
{
if(b == 0) {x = 1, y = 0; return a;}
ll d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
inline ll inver(ll a, ll p)
{
ll x, y, d = exgcd(a, p, x, y);
x = (x % p + p) % p;
return x;
}
bool exCRT(ll &m0, ll &a0, ll m1, ll a1)
{
// debug("%lld %lld %lld %lld\n", m0, a0, m1, a1);
ll t = a1 - a0;
ll x, y, d = exgcd(m0, m1, x, y);
if(t % d) return 0;
ll M = m0 / d * m1;
m1 /= d, t /= d;
t = (t % M + M) % M;
x = mul((x % m1 + m1) % m1, t, M);
a0 = (mul(x, m0, M) + a0) % M;
m0 = M;
return 1;
}
ll solve()
{
if(m == 0) return -1;
ll mn = 0;
ll M = 1, A = 0;
for(int i = 1; i <= n; ++i)
{
multiset<ll>::iterator it = s.upper_bound(a[i]);
if(it != s.begin()) --it;
int b = *it;
s.erase(it);
Cmax(mn, (a[i] + b - 1) / b);
ll d = gcd(b, p[i]);
if(a[i] % d) return -1;
b /= d, p[i] /= d, a[i] /= d;
a[i] = mul(a[i], inver(b, p[i]), p[i]);
if(!exCRT(M, A, p[i], a[i])) return -1;
s.insert(c[i]);
}
if(A < mn) A += (mn - A + M - 1) / M * M;
else if(A > mn) A -= (A - mn) / M * M;
return A;
}
int main()
{
freopen("dragon.in", "r", stdin);
freopen("dragon.out", "w", stdout);
read(T);
for(int kase = 1; kase <= T; ++kase)
{
read(n), read(m);
for(int i = 1; i <= n; ++i) read(a[i]);
for(int i = 1; i <= n; ++i) read(p[i]);
for(int i = 1; i <= n; ++i) read(c[i]);
s.clear();
for(int i = 1; i <= m; ++i)
{
int x; read(x);
s.insert(x);
}
printf("%lld\n", solve());
}
return 0;
}
Summary
學會了一元二次方程轉同餘方程的方法.