【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;
}