• loj 2721 [NOI2018] 屠龙勇士


    loj 2721 [NOI2018] 屠龙勇士

    https://loj.ac/problem/2721

    (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 ceil) 就好了.

    现在可以写作

    [bx + kp_i = a_i ]

    这是一个一元二次方程,由于有 (n) 个这样的方程需要合并,所以我们将它转化为关于 (x) 的同余方程的形式

    具体步骤就是,设 (d = gcd(b, p_i)) ,若 (d mid 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
    ", 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
    ", solve());
    	} 
    	return 0;
    } 
    

    Summary

    学会了一元二次方程转同余方程的方法.

  • 相关阅读:
    【bzoj2882】工艺 后缀自动机+STL-map
    【bzoj3884】上帝与集合的正确用法 扩展欧拉定理
    【bzoj1475】方格取数 网络流最小割
    【bzoj4825】[Hnoi2017]单旋 线段树+STL-set
    【bzoj4448】[Scoi2015]情报传递 主席树
    【bzoj1803】Spoj1487 Query on a tree III DFS序+主席树
    【bzoj2127】happiness 网络流最小割
    【bzoj2431】[HAOI2009]逆序对数列 dp
    【bzoj4245】[ONTAK2015]OR-XOR 贪心
    【bzoj4066】简单题 KD-tree
  • 原文地址:https://www.cnblogs.com/ljzalc1022/p/13498352.html
Copyright © 2020-2023  润新知