传送门
数论与数据结构相结合的好题.
既然一定有环,那么变量就会循环,对于一个长度为的环,可以得到以下方程组:
这个方程组怎么解,当然是整体代入啦.
这样,只要我们求出,那么整个环及连进来的点的值都可以求出.(由此看出,每个点都要保存两个系数)
关于求的细节:设
若
若,当时有无穷组解;当,无解.
否则,
我们可以用两种方法:
- (先求乘法逆元)
void exgcd(int a,int b,int &x,int &y)
{
if(!b)x=1,y=0;
else exgcd(b,a%b,y,x),y-=(a/b)*x;
}
因为边会变,所以可以想到用动态树.
但是有环要怎么处理呢,我们可以把一条边拆掉,并用splay节点中的一个变量记录指向.(这条边的另一个点即为有向树的根)(详见代码中的dfs函数)
这样每个点都要维护两个系数,意义为:.
在旋转等操作中需要维护这两个系数.
最后,提供一个小小的优化:先预处理出的乘法逆元(毕竟询问多)
设
如果用快速幂的话,预处理复杂度为.
下面提供一个复杂度为的预处理方法:
inv[1]=1;
for(int i=2;i<mod;i++)inv[i]=(mod-mod/i)*inv[mod%i]%mod;
为什么?证明:
#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
#define g getchar()
#define lc tr[x].son[0]
#define rc tr[x].son[1]
using namespace std;
const int mod=10007,N=30010;
void qr(int &x)
{
char c=g;bool v=x=0;
while(!(isdigit(c)||c=='-'))c=g;
if(c=='-')v=1,c=g;
while(isdigit(c))x=x*10+c-'0',c=g;
if(v)x=-x;
}
void write(int x)
{
if(x/10)write(x/10);
putchar(x%10+'0');
}
void pri(int x)
{
if(x<0)putchar('-'),x=-x;
write(x);puts("");
}
struct data//我们把环拆成树,去掉一条边(用sf记录这条删掉的边)
{
int k,b;
data(){k=1;b=0;}
data(int k,int b):k(k),b(b){}
int sol(int x){return (k*x+b)%mod;}
data operator +(const data a)
{//每个点都用sf的x表出自己,由于sf是环中深度最大的点,所以把sf吊起,他就会自己表出自己,进而求出x,表出整个环的值
data c;
c.k=k*a.k%mod;
c.b=(a.k*b+a.b)%mod;
return c;
}//x1=k1*xt+b1,x2=k2*x1+b2=k2*(k1*xt+b1)+b2=k1*k2*xt+b1*k2+b2
}d[N],s[N];
struct node{int f,son[2],sf;}tr[N];
int inv[mod];//逆元
bool v[N],in[N];//是否遍历过,是否入栈
void dfs(int x)
{
in[x]=v[x]=1;
int f=tr[x].f;
if(in[f])
{
tr[x].f=0;
tr[x].sf=f;
}
if(!v[f])dfs(f);
in[x]=0;
}
void update(int x){s[x]=s[lc]+d[x]+s[rc];}
bool rt(int x){return tr[tr[x].f].son[0]!=x&&tr[tr[x].f].son[1]!=x;}
void rotate(int x,int w)
{
int f=tr[x].f,ff=tr[f].f,r,R;
r=tr[x].son[w];R=f;tr[R].son[1-w]=r;if(r)tr[r].f=R;
r=x;R=ff;if(tr[R].son[0]==f)tr[R].son[0]=r;else if(tr[R].son[1]==f)tr[R].son[1]=r;tr[r].f=R;
r=f;R=x;tr[R].son[w]=r;tr[r].f=R;update(f);update(x);
}
void splay(int x)
{
while(!rt(x))
{
int f=tr[x].f;
if(rt(f))rotate(x,tr[f].son[0]==x);
else
{
int ff=tr[f].f,a=(tr[f].son[0]==x),b=(tr[ff].son[0]==f);
rotate(a^b?x:f,a);rotate(x,b);
}
}
}
void access(int x){for(int y=0;x;x=tr[y=x].f)splay(x),rc=y,update(x);}
int find_root(int x)//不能makeroot,因为要保证sf为环中深度最大的点
{
access(x);splay(x);
while(lc)x=lc;
return x;
}
void link(int x,int y){access(x);splay(x);tr[x].f=y;}
void cut(int x)
{
access(x);splay(x);
tr[lc].f=0;lc=0;
update(x);
}
int query(int x)
{
access(x);splay(x);
data s1=s[x];
int tx=find_root(x),f=tr[tx].sf;
access(f);splay(f);
data s2=s[f];//x=kx+b
if(!s2.k)return s1.sol(s2.b);//x=b
if(s2.k==1)return s2.b?-1:-2;//x=x+1(无解),x=x(无穷解)
return s1.sol((mod-inv[s2.k-1])*s2.b%mod);
}
bool check(int x,int tx)//判断x是否在环上
{
int y=tr[tx].sf;
if(x==y)return 1;
access(y);splay(y);splay(x);
return !rt(y);//如果y的位置变了,说明x在环上
}
void change(int x,int y,int k,int b)
{
access(x);splay(x);d[x]=(data){k,b};update(x);
int tx=find_root(x),ty;
if(x==tx)
{
ty=find_root(y);
if(ty==x)tr[x].sf=y;//x,y已经联通了,此时在加边就成环了
else tr[x].sf=0,link(x,y);//断开原来的树上没有的边,再连边。
}
else
{
//在环上
if(check(x,tx))cut(x),link(tx,tr[tx].sf),tr[tx].sf=0;//环断成树了
else cut(x);//cut完,x已经成了深度最小的
ty=find_root(y);
if(ty==x)tr[x].sf=y;
else link(x,y);
}
}
int main()
{
int n;qr(n);
inv[1]=1;
for(int i=2;i<mod;i++)inv[i]=(mod-mod/i)*inv[mod%i]%mod;/*
t=M/i;k=M%i;
t*i+k≡0(mod M)
-t*i≡k(mod M)
-t*inv[k]≡inv[i](mod M)
inv[i]≡(M-M/i)*inv[M%i](mod M)
*/
for(int i=1;i<=n;i++)
{
int k,b;qr(k);qr(tr[i].f);qr(b);
d[i]=s[i]=(data){k,b};
}
for(int i=1;i<=n;i++)if(!v[i])dfs(i);
int m;qr(m);
while(m--)
{
int x,k,b,f;
char s[3];scanf("%s",s);qr(x);
switch(s[0]){
case 'A':pri(query(x));break;
case 'C':qr(k);qr(f);qr(b);
change(x,f,k,b);break;
}
}
return 0;
}