【EXCRT】【平衡树】【exgcd】[NOI2018] 屠龙勇士 题解

P4774 [NOI2018] 屠龙勇士

题面

小 D 最近在网上发现了一款小游戏。游戏的规则如下:

  • 游戏的目标是按照编号 \(1 \rightarrow n\) 顺序杀掉 \(n\) 条巨龙,每条巨龙拥有一个初始的生命值 \(a_i\) 。同时每条巨龙拥有恢复能力,当其使用恢复能力时,它的生命值就会每次增加 \(p_i\) ,直至生命值非负。只有在攻击结束后且当生命值 恰好\(0\) 时它才会死去。
  • 游戏开始时玩家拥有 \(m\) 把攻击力已知的剑,每次面对巨龙时,玩家只能选择一把剑,当杀死巨龙后这把剑就会消失,但作为奖励,玩家会获得全新的一把剑。

小 D 觉得这款游戏十分无聊,但最快通关的玩家可以获得 ION2018 的参赛资格,于是小 D 决定写一个笨笨的机器人帮她通关这款游戏,她写的机器人遵循以下规则:

  • 每次面对巨龙时,机器人会选择当前拥有的,攻击力不高于巨龙初始生命值中攻击力最大的一把剑作为武器。如果没有这样的剑,则选择 攻击力最低 的一把剑作为武器。
  • 机器人面对每条巨龙,它都会使用上一步中选择的剑攻击巨龙固定的 \(x\) 次,使巨龙的生命值减少 \(x \times ATK\)
  • 之后,巨龙会不断使用恢复能力,每次恢复 \(p_i\) 生命值。若在使用恢复能力前或某一次恢复后其生命值为 \(0\) ,则巨龙死亡,玩家通过本关。

那么显然机器人的攻击次数是决定能否最快通关这款游戏的关键。小 D 现在得知了每条巨龙的所有属性,她想考考你,你知道应该将机器人的攻击次数 \(x\) 设置为多少,才能用最少的攻击次数通关游戏吗?

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

输入

2
3 3
3 5 7
4 6 10
7 3 9
1 9 1000
3 2
3 5 6
4 8 7
1 1 1
1 1

输出

59
-1

解释

第一组数据:

  • 开始时拥有的剑的攻击力为 \(\{1,9,10\}\),第 \(1\) 条龙生命值为 \(3\),故选择攻击力为 \(1\) 的剑,攻击 \(59\) 次,造成 \(59\) 点伤害,此时龙的生命值为 \(-56\),恢复 14 次后生命值恰好为 \(0\),死亡。

  • 攻击力为 \(1\) 的剑消失,拾取一把攻击力为 \(7\) 的剑,此时拥有的剑的攻击力为
    \(\{7,9,10\}\),第 2 条龙生命值为 \(5\),故选择攻击力为 \(7\) 的剑,攻击 \(59\) 次,造成 \(413\) 点伤害,此时龙的生命值为 \(-408\),恢复 \(68\) 次后生命值恰好为 \(0\),死亡。

  • 此时拥有的剑的攻击力为 \(\{3,9,10\}\),第 \(3\) 条龙生命值为 \(7\),故选择攻击力为 \(3\) 的剑,攻击 \(59\) 次,造成 \(177\) 点伤害,此时龙的生命值为 \(-170\),恢复 \(17\) 次后生命值恰好为 0,死亡。

  • 没有比 \(59\) 次更少的通关方法,故答案为 \(59\)

第二组数据:
不存在既能杀死第一条龙又能杀死第二条龙的方法,故无法通关,输出 \(-1\)

题意

对于每一条龙,初始生命值确定,恢复能力确定,使用的剑攻击力确定,因此可以说,求最小的正整数\(x\),使得\(ATK_ix\equiv a_i\pmod {p_i}\)

思路

看上去就觉得很像EXCRT的式子。但是前面多了一个\(ATK_i\),而且他还不一定有逆元。那么怎么做掉他呢?

先考虑把同余方程转化为不定方程,\(A_ix\equiv a_i\pmod{p_i}\Rightarrow A_ix+p_iy=a_i\)。如果\(\gcd(A_i,p_i)|a_i\)则有解,否则不能杀死这条龙。

有解,那么解出不定方程\(A_ix+p_iy=\gcd(A_i,p_i)\)的最小正整数解\(x_0\),而原方程的通解就是\(x=x_0\dfrac {a_i}{\gcd(A_i,p_i)}+k\dfrac{p_ia_i}{\gcd(A_i,p_i)}\quad(k\in\Z)\)

那么就可以有新的同余方程\(x\equiv \dfrac{a_i}{\gcd(A_i,p_i)}x_0\pmod{\dfrac{p_i}{\gcd(A_i,p_i)}}\)

这个方程可以很愉快地使用EXCRT

所以我们先用exgcd把同余方程的系数去掉,然后再用EXCRT求解方程即可。

对于EXCRT处理的两个同余方程\(x\equiv a_i\pmod {p_i},x\equiv a_{i+1}\pmod{p_{i+1}}\),假定\(a_{i+1}>a_{i}\)

那么转化为不定方程就会有\(x=a_i+k_1p_{i}=a_{i+1}+k_2p_{i+1}\)

移项,得到\(k_1p_i-k_2p_{i+1}=a_{i+1}-a_i\),解出模\(p_{i+1}\)意义下的\(k_1\),于是原来两个不定方程的通解就是\(x=a_i+k_1p_i+k\mathrm{lcm}(p_i,p_{i+1})\),得到的新的同余方程就是\(x\equiv a_i+k_1p_i\pmod{\mathrm{lcm}(p_i,p_{i+1})}\)

然后在错误过很多次之后就会发现,有三个地方要用龟速乘,不然long long都爆精度,都在代码中注释出来了。

(不要问我为什么要手写平衡树)

View Code
#include <cstdio>
#include <algorithm>
#include <functional>
#ifndef ONLINE_JUDGE
#define FILEIO
#endif // ONLINE_JUDGE
#ifdef FILEIO
FILE *infile=fopen( "datas/" __FILE__ ".in","r"), *outfile=fopen( "datas/" __FILE__ ".out","w");
#define scanf(x...) fscanf(infile,x)
#define printf(x...) fprintf(outfile,x)
#else // FILEIO
#define infile stdin
#define outfile stdout
#endif // FILEIO

#define IOMxn (1<<12)
#ifdef IOMxn
char pI[IOMxn];
char *Ip1=pI,*Ip2=pI;
#define getchar()\
((Ip1==Ip2 && (Ip2=(Ip1=pI)+fread(pI,1,IOMxn,infile),Ip1==Ip2))?(-1):(*(Ip1++)))
char pO[IOMxn];
char *Op=pO;
#define putchar(a)\
(((Op-pO==IOMxn && fwrite(Op=pO,1,IOMxn,outfile)),*(Op++))=a)
#endif // IOMxn
template <typename T>
inline void read(T& x) {
	x=0;
	static char ch;
	do ch=getchar(); while(ch>'9' or ch<'0');
	do {
		x=(x<<1)+(x<<3)+(ch^48); ch=getchar();
	}while(ch>='0' and ch<='9');
	return ;
}

template <typename T>
void write(T n) {
	if(n<0) putchar('-'), n*=-1;
	if(n>9) write(n/10);
	putchar((n%10)^48);
}

template <typename T,class Comp=std::less<T> > class Splay;

template <typename T,class Comp>
class Splay {
protected:
	class splay {
		size_t sze,cnt;
		splay *sn[2],*fa;
		T dat;
		splay() { sze=cnt=0; sn[0]=sn[1]=fa=0x0; }
		splay(splay* s,splay *f) { sze=cnt=0; sn[0]=sn[1]=s; fa=f; }
		friend class Splay;
	}*rt;
	Comp cmp;
#define ls sn[0]
#define rs sn[1]
	splay* const None;
	inline size_t maintain(splay* x) { None->fa=None->ls=None->rs=None; return x->sze=x->cnt+x->ls->sze+x->rs->sze; }
	inline int get(splay*x) { return x==x->fa->rs; }
	inline size_t rotate(splay* x) {
		if(x==rt or x==None) return maintain(x);
		splay* y=x->fa; const int chk=get(x);
		y->sn[chk]=x->sn[chk^1]; x->sn[chk^1]->fa=y;
		x->fa=y->fa;
		if(y->fa==None) rt=x; else y->fa->sn[get(y)]=x;
		y->fa=x; x->sn[chk^1]=y;
		maintain(y); return maintain(x);
	}
	inline splay* up(splay* x, splay*top=0x0) {
		if(x==None) return x;
		if(!top) top=None;
		while(x->fa!=top) {
			if(x->fa->fa!=top) rotate(get(x)==get(x->fa)? x->fa:x);
			rotate(x);
		}
		return x;
	}
	inline splay* ins(T x) {
		splay* p=rt;
		while(p->cnt) {
			if(cmp(x,p->dat)) {
				if(p->ls==None) p->ls=new splay(None,p);
				p=p->ls;
			}else if(cmp(p->dat,x)) {
				if(p->rs==None) p->rs=new splay(None,p);
				p=p->rs;
			}else break;
		}
		++p->cnt;
		p->dat=x;
		maintain(p);
		return up(p);
	}
	inline splay* fd(T x) {
		splay* p=rt;
		while(p!=None) {
			if(cmp(x,p->dat)) p=p->ls;
			else if(cmp(p->dat,x)) p=p->rs;
			else break;
		}
		return p;
	}
	inline splay* min(splay *x) { while(x->ls!=None) x=x->ls; return x; }
	inline splay* max(splay *x) { while(x->rs!=None) x=x->rs; return x; }
	inline int erase() {
		if(rt->ls==None and rt->rs==None) { delete rt; rt=None; }
		else if((rt->ls==None)^(rt->rs==None)) {
			rt=rt->sn[rt->ls==None];
			delete rt->fa;
			rt->fa=None;
		}else {
			up(max(rt->ls),rt);
			rt->ls->rs=rt->rs; rt=rt->rs->fa=rt->ls;
			delete rt->fa; rt->fa=None;
		}
		maintain(rt);
		return 0;
	}
	inline splay* pre(T x) {
		up(ins(x));
		if(--rt->cnt) {
			return rt;
		}
		splay* res=max(rt->ls);
		erase();
		return res;
	}
public:
	Splay():None(new splay) { rt=None->fa=None->ls=None->rs=None; }
	class iterator {
		splay* M_current;
		iterator(splay* x=0x0):M_current(x) {}
	public:
		T operator*() const { return M_current->dat; }
		friend class Splay;
	};
	iterator insert(T x) {
		if(rt==None) {
			rt=new splay(None,None); rt->dat=x; rt->cnt=1; maintain(rt);
			return rt;
		}
		return iterator(ins(x));
	}
	int remove(T x,size_t c=1) {
		splay *p=fd(x); if(p==None) return 1;
		if(--(p=up(p))->cnt) return 0;
		return erase();
	}
	iterator begin() { return iterator(up(min(rt))); }
	iterator prefix(T x) { return iterator(pre(x)); }
#undef ls
#undef rs
};

Splay<long long> swords[5];

inline long long plus(long long x,long long y,const long long& mod) {
	x+=y-mod; return x+((x>>63)&mod);
}
inline long long minus(long long x,long long y,const long long& mod) {
	x-=y; return x+((x>>63)&mod);
}
inline long long times(long long a,long long b,const long long& mod) {
	long long res=0,rec=a;
	while(b) {
		if(b&1) res=plus(res,rec,mod);
		rec=plus(rec,rec,mod); b>>=1;
	}
	return res;
}

long long exgcd(long long& x,long long& y,long long a,long long b,const long long& mod) {
	if(b==0) {
		x=1; y=0; return a;
	}
	long long res=exgcd(y,x,b,a%b,mod);
	y=minus(y,times(a/b,x,mod),mod);
	return res;
}

long long gcd(long long a,long long b) {
	if(b==0) return a;
	return gcd(b,a%b);
}

int T;
long long n,m,a[100001],p[100001],A[100001],s[100001],x,y;
inline long long int Main() {
	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(s[i]);
	for(int i=1;i<=m;++i) {
		read(x); swords[T].insert(x);
	}
	long long mn=0;
	for(int i=1;i<=n;++i) {
		if((A[i]= *swords[T].begin())<a[i]) A[i]= *swords[T].prefix(a[i]);
		swords[T].remove(A[i]); swords[T].insert(s[i]);
		mn=std::max(mn,a[i]/A[i]+bool(a[i]%A[i]));
	}
	for(int i=1;i<=n;++i) a[i]%=p[i];
	for(int i=1;i<=n;++i) {
		const long long g=exgcd(x,y,A[i],p[i],p[i]);
		if(a[i]%g) return -1;
		x=(x%p[i]+p[i])%p[i];
		a[i]=times(a[i]/g,x,p[i]/=g); // a[i]=1e12,p[i]=1e8,g=1,要用龟速乘
	}
	for(int i=1;i<n;++i) {
		if(a[i]>a[i+1]) a[i]^=a[i+1]^=a[i]^=a[i+1], p[i]^=p[i+1]^=p[i]^=p[i+1];
		const long long g=exgcd(x,y,p[i],p[i+1],p[i+1]),c=a[i+1]-a[i];
		if(c%g) return -1;
		x=(x%p[i+1]+p[i+1])%p[i+1];
		x=times(x,c/g,p[i+1]); // c=a[i]=1e12,p=1e8,要用龟速乘
		p[i+1]*=p[i]/g; a[i+1]=plus(a[i],times(x,p[i],p[i+1]),p[i+1]); // lcm=1e12, p=1e8, 要用龟速乘
	}
	if(a[n]<mn) a[n]+=p[n]*((mn-a[n])/p[n]+bool((mn-a[n])%p[n])); // 有可能出来结果是对的,但是没砍到负血
	return a[n];
}

int main() {
	read(T);
	while(T--) {
		write(Main()); putchar(10);
	}
#ifdef IOMxn
	fwrite(pO,1,Op-pO,outfile);
#endif // IOMxn
#ifdef FILEIO
	fclose(infile); fclose(outfile);
#undef scanf
#undef printf
#endif // FILEIO
	return 0;
}
posted @ 2022-07-25 16:34  IdanSuce  阅读(23)  评论(0编辑  收藏  举报