exCRT&Prüfer

中国剩余定理

\(\texttt{Description}\)

\[\begin{cases} x\equiv b_1 (\text{mod } a_1)\\ x\equiv b_2 (\text{mod } a_2)\\ \cdots\\ x\equiv b_n (\text{mod } a_n) \end{cases} \]

求出最小的 \(x\)

\(\texttt{Solution}\)

首先先将问题特殊化,如果只有两个数怎么做?

\[x=t_1\times a_1+b_1\\ x=t_2\times a_2+b_2\\ \]

然后就可以得到:\(t_1\times a_1-t_2\times a_2=b_2-b_1\)

然后用 \(\text{exgcd}\) 找到最小的满足条件的自然数 \(t_2\) 就可以了。

然后如果我们把通解带回原方程组,就可以得到:

\[x=t_2\times a_2+b_2+s\times \frac{a_1\times a_2}{\gcd(a_1,a_2)} \]

我们发现可以和下面的联立每次都要使后面一项尽可能的小。

所以这个 \(a_i\) 是质数是在迷惑什么?

void exgcd(__int128 x,__int128 y){
	if (y==0){a=1;b=0;return ;}
	exgcd(y,x % y);__int128 c=a;a=b;b=c-b*(x/y);
}
int main()
{
	n=read();
	for (i=1;i<=n;i++) A[i]=read(),B[i]=read(),B[i]%=A[i];
	if (n==1){
		ans=B[1];
		printf("%lld\n",ans);return 0;
	}
    for (i=2;i<=n;i++){
    	exgcd(A[i],A[i-1]);
		long long gd=__gcd(A[i],A[i-1]);
    	lft=A[i]/gd;rit=A[i-1]/gd;G=0;
    	if (B[i-1]-B[i]<0) a=-a,b=-b;
    	a=a*(abs(B[i-1]-B[i])/gd);b=b*(abs(B[i-1]-B[i])/gd);
    	if (a>=0) G=-(a/rit);
    	else G=(-a-1)/rit+1;
		lft=a+G*rit;
		if (i==n){ans=A[i]*lft+B[i];printf("%lld\n",ans);return 0;}
		B[i]=(A[i]*lft+B[i]) % (A[i]/gd*A[i-1]);A[i]=A[i]/gd*A[i-1];
	}
	 return 0;
}

Prüfer 序列

定义

长度为 \(n-2\),值域为 \([1,n]\) 的一种序列,可以表示 \(n\) 个点的带标号无根树。可以与 \(n\) 个点的完全图的生成树形成双射。

树到\(\texttt{Prüfer}\)

  • 找到剩余图中编号最小的叶子节点

  • 记录他在图中连接到的那个节点

  • 重复 \(n-2\)

有一种显然的做法是每次把叶子节点取出,父亲节点 \(\texttt{deg}-1\) ,然后如果又变成了叶子节点就把他加到堆里,复杂度是 \(\mathcal O(n\log n)\)

然后我们考虑到因为刚取出来的这个叶子节点是最小的,那么如果删去之后,父亲节点变成了叶子节点,并且编号比这个叶子节点要小,那么他一定可以直接选择,因为现存的叶子节点是没有比这个叶子节点要小的,然后复杂度就变成了 \(\mathcal O(n)\)

\(\texttt{Prüfer}\)到树

我们注意到一个点的出现次数就是 \(\texttt{deg}-1\),那么 \(\texttt{Prüfer}\) 序列中没有出现过的点,就是叶子节点。然后就是将这些所有点加进去,每次取出最小的,如果 \(\texttt{Prüfer}\) 序列中一个点出现完了,也把这个点加进去继续做,这时候时间复杂度是 \(\mathcal O(n\log n)\)

然后我们再看有没有单调性,是不是新的那个出现完的节点一定是最小的呢?

根据上面的序列构造的方法,可以发现这是显然的。

void Subtask1(){
	for (i=1;i<n;i++) fa[i]=read(),deg[fa[i]]++;
	now=1;
	for (i=1;i<=n-2;i++){
		while (deg[now]) now++;prufer[i]=fa[now];
		for (;i<=n-2;i++){
			deg[prufer[i]]--;
			if ((deg[prufer[i]])||(prufer[i]>now)) break;
			prufer[i+1]=fa[prufer[i]];
		}now++;
	}for (i=1;i<=n-2;i++) ans^=(i*prufer[i]);
	printf("%lld\n",ans);
}void Subtask2(){
	for (i=1;i<=n-2;i++) prufer[i]=read(),deg[prufer[i]]++;
	prufer[n-1]=n;now=1;
	for (i=1;i<=n-1;i++){
		while (deg[now]) now++;fa[now]=prufer[i];
		for (;i<=n-1;i++){
			deg[prufer[i]]--;
			if ((deg[prufer[i]])||(prufer[i]>=now)) break;
			fa[prufer[i]]=prufer[i+1];
		}now++;
	}for (i=1;i<=n-1;i++) ans^=(i*fa[i]);
	printf("%lld\n",ans);
}
posted @ 2022-03-25 20:33  OIer_Albedo  阅读(53)  评论(2)    收藏  举报