P4774 [NOI2018] 屠龙勇士

链接:P4774

前言:

交了18遍最后发现是多组数据没清空/ll


题意:

其实就是个扩中。


分析过程:

首先发现根据题目描述的选择剑的方式,每条龙对应的剑都是固定的,有查询前驱,后继(在该数不存在前驱时,最小值即为后继),和插入,删除操作,所以想到平衡树维护每条龙的剑的攻击力,记为b[i]。建议使用非旋treap,非常之好写。


根据题目描述,a[i]为每条龙生命值,p[i]为每条龙回复量。发现能够击杀这条龙的条件可以列成一个方程:

x b [ i ] − y p [ i ] = a [ i ] xb[i]-yp[i]=a[i] xb[i]yp[i]=a[i]

x x x 为攻击次数, y y y 为回复次数,转化为同余方程的形式为:

x b [ i ] ≡ a [ i ] ( m o d p [ i ] ) xb[i]\equiv a[i]\pmod {p[i]} xb[i]a[i](modp[i])

所以题目就被我们转化成了一个一元 n n n 次的不定方程组:

{ x b [ 1 ] ≡ a [ 1 ] ( m o d p [ 1 ] ) x b [ 2 ] ≡ a [ 2 ] ( m o d p [ 2 ] ) ⋮ x b [ n ] ≡ a [ n ] ( m o d p [ n ] ) \begin{cases}xb[1]\equiv a[1]\pmod {p[1]} \\ xb[2]\equiv a[2]\pmod {p[2]}\\ \vdots\\ xb[n]\equiv a[n]\pmod {p[n]}\end{cases} xb[1]a[1](modp[1])xb[2]a[2](modp[2])xb[n]a[n](modp[n])

x x x 的最小非负整数解。

这样的形式虽然不满足CRT和exCRT的形式,但我们可以从exCRT的思想得到启发。我们记录下前 m − 1 m-1 m1 个方程的通解 x = x 0 + t ∗ M x=x_0+t*M x=x0+tM,也就是说我们已知 x 0 x_0 x0 M M M

对于第 m m m 个方程 x b [ m ] − y p [ m ] = a [ m ] xb[m]-yp[m]=a[m] xb[m]yp[m]=a[m] ,将上述 x x x 带入,有

( x 0 + t ∗ M ) b [ i ] − y p [ i ] = a [ i ] (x_0+t*M)b[i]-yp[i]=a[i] (x0+tM)b[i]yp[i]=a[i]

x 0 b [ i ] + t ∗ M ∗ b [ i ] − y p [ i ] = a [ i ] x_0b[i]+t*M*b[i]-yp[i]=a[i] x0b[i]+tMb[i]yp[i]=a[i]

由于 x 0 , b [ i ] , M , p [ i ] , a [ i ] x_0,b[i],M,p[i],a[i] x0,b[i],M,p[i],a[i] 全部都已知,所以化为

t ∗ M b [ i ] − y ∗ p [ i ] = a [ i ] − x 0 b [ i ] t*Mb[i]-y*p[i]=a[i]-x_0b[i] tMb[i]yp[i]=a[i]x0b[i]

t a = M b [ i ] , t b = p [ i ] , t c = a [ i ] − x 0 b [ i ] ta=Mb[i],tb=p[i],tc=a[i]-x_0b[i] ta=Mb[i],tb=p[i],tc=a[i]x0b[i]

就有 t ∗ t a − y ∗ t b = t c t*ta-y*tb=tc ttaytb=tc

可以用扩展欧几里得求解 t = t 0 + q ∗ t b gcd ⁡ ( t a , t b ) t=t_0+q*\frac{tb}{\gcd(ta,tb)} t=t0+qgcd(ta,tb)tb

如果 t t t 无解那显然此题就无解。

为了这里看起来简洁一些,设 r = t b gcd ⁡ ( t a , t b ) r=\frac{tb}{\gcd(ta,tb)} r=gcd(ta,tb)tb

t = t 0 + q ∗ r t=t_0+q*r t=t0+qr

发现我们不停带入化简求出了前 m − 1 m-1 m1 个方程的解 x = x 0 + t ∗ M x=x_0+t*M x=x0+tM t t t的范围,所以再将 t t t 带入该式,有:

x = x 0 + ( t 0 + q ∗ r ) ∗ M x=x_0+(t_0+q*r)*M x=x0+(t0+qr)M

x = x 0 + t 0 ∗ M + q ∗ r ∗ M x=x_0+t_0*M+q*r*M x=x0+t0M+qrM

还看不出来吗?那就再括起来:

x = ( x 0 + t 0 ∗ M ) + q ∗ ( r ∗ M ) x=(x_0+t_0*M)+q*(r*M) x=(x0+t0M)+q(rM)

这个写法是不是和 x = x 0 + t ∗ M x=x_0+t*M x=x0+tM 有点像?没错,这就是合并了两个方程之后的解。

发现我们完全通过柿子和推导得出了合并方程的方法,只需要做一次扩展欧几里得。那么对于第一个方程怎么办呢,我们当然可以特殊处理第一个方程,求出它的解。还有另一种巧妙的办法,就是将 x x x 一开始的取值范围设为 Z \mathbb{Z} Z,只需将 M M M 设为 1 1 1,即 x = x 0 + t ∗ 1 x=x_0+t*1 x=x0+t1 ,这里的 x 0 x_0 x0 对结果应该没有影响,我测试了几个不同的初值都能过,这样就不用单独处理第一个方程了。


坑1:

注意我们转化方程 x b [ i ] − y p [ i ] = a [ i ] xb[i]-yp[i]=a[i] xb[i]yp[i]=a[i] 的过程是有缺陷的,根据题意,这里的 攻击次数 x x x 和回复次数 y y y 显然都应该是非负数而且有一定限制,翻译成人话就是说你砍出来的伤害至少要大于这条龙的血量。再翻译成数学语言就是:

∀ i   ( i ∈ [ 1 , n ] ) , x b [ i ] ≥ a [ i ] \forall i\ (i\in \left [ 1,n \right ]),xb[i]\geq a[i] i (i[1,n]),xb[i]a[i]

转化不等式: x ≥ a [ i ] b [ i ] x\geq \frac{a[i]}{b[i]} xb[i]a[i]

所以用double类型记录最大的 a [ i ] b [ i ] \frac{a[i]}{b[i]} b[i]a[i],把最后的解处理一下即可。


坑2:

因为此题数据范围较大,long long会溢出,所以需要在各种地方取模并使用龟速乘防止溢出。由于我们记录的一直是通解,所以 x 0 x_0 x0 的值并不要求最小,只是为了避免溢出所以让 x 0 x_0 x0 每次对 M M M 取模。


坑3:

就是我亲身踩了17次的注意平衡树每次会有剩下的剑还在树中,注意清空。


代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=1e5+5;

int read(){
	int p=0,f=1;
	char c=getchar();
	while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9'){p=p*10+c-'0';c=getchar();}
	return p*f;
}

//treap-------------------
#define v(x) kin[x].v
#define lc(x) kin[x].lc
#define rc(x) kin[x].rc
#define siz(x) kin[x].siz
#define rnd(x) kin[x].rnd
struct k{
	int v,lc,rc,siz,rnd;
}kin[2*maxn];
int rt,cnt;
int newnode(int x){
	rnd(++cnt)=rand();
	siz(cnt)=1;
	v(cnt)=x;
	lc(cnt)=rc(cnt)=0;
	return cnt;
}
void pushup(int x){
	siz(x)=siz(lc(x))+siz(rc(x))+1;
}
int merge(int x,int y){
	if(!x||!y)return x|y;
	if(rnd(x)<rnd(y)){
		rc(x)=merge(rc(x),y);
		pushup(x);
		return x;
	}
	else{
		lc(y)=merge(x,lc(y));
		pushup(y);
		return y;	
	}
}
void split(int p,int k,int &x,int &y){
	if(!p){x=y=0;return ;}
	if(v(p)<=k){
		x=p;
		split(rc(p),k,rc(p),y);
		pushup(p);
	}
	else{
		y=p;
		split(lc(p),k,x,lc(y));
		pushup(p);
	}
}
void insert(int v){
	int x,y,z;
	x=y=z=0;
	split(rt,v,x,z);
	y=newnode(v);
	rt=merge(merge(x,y),z);
}
void del(int v){
	int x,y,z;
	x=y=z=0;
	split(rt,v,x,z);
	split(x,v-1,x,y);
	y=merge(lc(y),rc(y));
	rt=merge(merge(x,y),z);	
}
int getb(int a){
	int x,y;
	x=y=0;
	split(rt,a,x,y);
	if(x){
		int now=x;
		while(rc(now))now=rc(now);
		now=v(now);
		rt=merge(x,y);
		del(now);
		return now;		
	}
	else{
		int now=y;
		while(lc(now))now=lc(now);
		now=v(now);
		rt=merge(x,y);
		del(now);
		return now;
	}
}
//treap----------------------------

int exgcd(int a,int b,int &x,int &y){
	if(a<b)return exgcd(b,a,y,x);
	if(a%b==0){
		x=0;y=1;
		return b;
	}
	else{
		int tx,ty;
		int d=exgcd(b,a%b,tx,ty);
		x=ty;
		y=tx-a/b*ty;
		return d;
	}
}
int gsc(int ta,int tb,int mod){
	if(!ta||!tb)return 0;
	int ans=0,f=1;
	if(ta<0)ta=-ta,f*=-1;
	if(tb<0)tb=-tb,f*=-1;
	ta%=mod;
	tb%=mod;
	while(tb){
		if(tb&1)ans=(ans+ta)%mod;
		ta=(ta+ta)%mod;
		tb>>=1;
	}
	return ans*f;
}
int T;
int n,m;
int a[maxn],p[maxn],q[maxn],b[maxn];
int x0,M;
signed main(){
	T=read();
	while(T--){
		cnt=0,rt=0;
		n=read(),m=read();
		for(int i=1;i<=n;i++)
			a[i]=read();
		for(int i=1;i<=n;i++)
			p[i]=read();	
		for(int i=1;i<=n;i++)
			q[i]=read();
		for(int i=1;i<=m;i++){
			int temp=read();
			insert(temp);
		}
		for(int i=1;i<=n;i++){
			b[i]=getb(a[i]);
			insert(q[i]);
		}
		x0=0,M=1;
		int ta,tb,tc,t,y,d,flag=1;
		double mx=0;
		for(int i=1;i<=n;i++){
			ta=M*b[i],tb=p[i],tc=a[i]-x0*b[i];
			d=exgcd(ta,tb,t,y);
			if(tc%d!=0){
				printf("-1\n");
				flag=0;
				break;
			}
			ta/=d,tb/=d,tc/=d;
			t=(t%tb+tb)%tb;
			x0+=gsc(gsc(tc,t,M*tb),M,M*tb);
			M*=tb;
			x0=(x0%M+M)%M;
			if(double(a[i])/b[i]>mx)
				mx=double(a[i])/b[i];
		}
		while(x0<mx)x0+=M;
		if(flag)
			printf("%lld\n",x0);
	}
	return 0;
}

题外话:

做完感觉自己就是屠龙勇士,另外感觉这道题很好的揭示了平衡树这一工具人的作用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值