zoukankan      html  css  js  c++  java
  • BZOJ 2759: 一个动态树好题

    传送门
    数论与数据结构相结合的好题.

    ,ip[i],,.:如果,我们令i向p[i]连一条有向边的话,这个有向图一定会有环,形成基环外向森林.如图:在这里插入图片描述
    既然一定有环,那么变量就会循环,对于一个长度为tt的环,可以得到以下方程组:
    {x1k1xt+b1(mod10007)x2k2x1+b2(mod10007)xtktxt1+bt(mod10007)egin{cases}x_1≡k_1*x_t+b_1(operatorname{mod} 10007) \x_2≡k_2*x_1+b_2(operatorname{mod} 10007)\……\x_t≡k_t*x_{t-1}+b_t(operatorname{mod} 10007)end{cases}

    这个方程组怎么解,当然是整体代入啦.

    {x1k1xt+b1(mod10007)x2k2x1+b2k2(k1xt+b1)+b2k1k2xt+b1k2+b2(mod10007)x3k3x2+b3k3(k1k2xt+b1k2+b2)+b3k1k2k3xt+b1k2k3+b2k3+b3(mod10007)xtktxt1+bt(i=1tki)+i=1t(bij=i+1tkj)(mod10007)()egin{cases}x_1≡k_1*x_t+b_1(operatorname{mod} 10007) \x_2≡k_2*x_1+b_2≡k_2*(k_1*x_t+b_1)+b2≡k_1*k_2*x_t+b_1*k_2+b_2(operatorname{mod} 10007)\x_3≡k_3*x_2+b_3≡k_3*(k_1*k_2*x_t+b_1*k_2+b_2)+b_3≡k_1*k_2*k_3*x_t+b_1*k_2*k_3+b_2*k_3+b_3(operatorname{mod} 10007)\……\x_t≡k_t*x_{t-1}+b_t≡(prod_{i=1}^t k_i)+sum_{i=1}^t(b_i*prod_{j=i+1}^t k_j)(operatorname{mod} 10007)(数学归纳法)end{cases}
    这样,只要我们求出xtx_t,那么整个环及连进来的点的值都可以求出.(由此看出,每个点都要保存两个系数k,bk,b)

    关于求xt(x)x_t(以下简写为x)的细节:设xkx+b(mod10007)x≡kx+b(operatorname{mod} 10007)
    k=0,x=bk=0,x=b
    k=1k=1,当b=0b=0时有无穷组解;当b0b e 0时,无解.
    否则,(k1)xb(mod10007)(k-1)x≡-b(operatorname{mod} 10007)
    我们可以用两种方法:

    1. exgcd(k1,10007,x,y)exgcd(k-1,10007,x,y)
    2. x=(pow(k1,10005)(10007b)mod10007)x=(pow(k-1,10005)*(10007-b)operatorname{mod} 10007)(先求乘法逆元)
    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节点中的一个变量sf(special  father)sf(special ~~ father)记录指向.(这条边的另一个点即为有向树的根)(详见代码中的dfs函数)
    这样每个点ii都要维护两个系数kbk、b,意义为:xi=kxroot.sfbx_i=k*x_{root.sf}*b.
    在旋转等操作中需要维护这两个系数.

    最后,提供一个小小的优化:先预处理出[0,10007)[0,10007)的乘法逆元(毕竟询问多)
    mod=10007mod=10007
    如果用快速幂的话,预处理复杂度为O(modlogmod)O(mod*log_{mod}).
    下面提供一个复杂度为O(mod)O(mod)的预处理方法:

    inv[1]=1;
    for(int i=2;i<mod;i++)inv[i]=(mod-mod/i)*inv[mod%i]%mod;
    

    为什么?证明:

    M=10007,t=M/i;k=Mmodi; 设M=10007,t=leftlfloor{M/i} ight floor;k=M operatorname{mod} i;
    ti+k0(modM)t*i+k≡0( operatorname{mod}M)
    tik(modM)-t*i≡k( operatorname{mod}M)
    tinv[k]inv[i](modM)-t*inv[k]≡inv[i]( operatorname{mod}M)
    inv[i](MM/i)inv[Mmodi](modM)inv[i]≡(M-leftlfloor{M/i} ight floor)*inv[M operatorname{mod}i]( operatorname{mod}M)

    #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;
    }
    
  • 相关阅读:
    看到一篇好文章 和大家分享《别让灵魂赶不上自己的脚步!》
    第二次结对编程作业
    css的用法
    MicrosoftWord2013基本用法
    使用winshark分析三次握手,四次挥手
    取石子(博弈游戏)
    java.lang.NullPointerException的可能原因及处理
    Git的用法
    C++ 各种基本类型间的转换
    Servlet技术(使用myeclipse)
  • 原文地址:https://www.cnblogs.com/zsyzlzy/p/12373891.html
Copyright © 2011-2022 走看看