exCRT&Prüfer

中国剩余定理

Description

{xb1(mod a1)xb2(mod a2)xbn(mod an)

求出最小的 x

Solution

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

x=t1×a1+b1x=t2×a2+b2

然后就可以得到:t1×a1t2×a2=b2b1

然后用 exgcd 找到最小的满足条件的自然数 t2 就可以了。

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

x=t2×a2+b2+s×a1×a2gcd(a1,a2)

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

所以这个 ai 是质数是在迷惑什么?

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 序列

定义

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

树到Prüfer

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

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

  • 重复 n2

有一种显然的做法是每次把叶子节点取出,父亲节点 deg1 ,然后如果又变成了叶子节点就把他加到堆里,复杂度是 O(nlogn)

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

Prüfer到树

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

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

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

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 @   OIer_Albedo  阅读(41)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示