【洛谷6730】[WC2020] 猜数游戏(数论)

点此看题面

  • 给定一个长度为(n)的序列,其中每个数都在([1,p))范围内,且各不相同。
  • 规定当你选了序列中的某一个数(x),则所有(x^i\%p)都会被选中。
  • 定义一个序列的代价为选中整个序列至少需要选的数的个数,求给定序列所有子序列的代价之和。
  • (nle5 imes10^3,ple10^8)且为某个质数的幂

建图:判断连边关系

考虑我们暴力建出一张有向图,如果选中了(i)能选中(j),就连一条从(i)(j)的边。

(p)是质数(pr)的幂,然后计算出(a_i=v_i imes pr^{k_i},a_j=v_j imes pr^{k_j})

首先,如果(k_i,k_j)中一个为(0)一个不为(0),显然不可能。

否则,如果(k_i,k_j)都不为(0),则当且仅当(a_i^{frac{k_j}{k_i}}equiv a_j(mod p))(i)才能向(j)连边。

(k_i,k_j)都为(0)的情况就有些麻烦了。

(a_i)为例,我们求出最大的(w_i)满足(a_i^{frac{phi(p)}{w_i}}equiv1(mod p)),相当于(frac{phi(p)}{w_i})就是(a_i)的最小周期。

这一过程中只要枚举(phi(p))的所有质因子看看给(w_i)乘上这个质因子之后之后是否依然能满足条件就好了。注意对于每个(a_i)可以事先把(w_i)预处理出来。

这样一来(i)能到(j)的充要条件就是(frac{phi(p)}{w_j}|frac{phi(p)}{w_i}),即(w_i|w_j)

注意这个过程中要特判(a=1)的情况,可以假设它的(w)值为(0),表示任意数能到它,而它不能到任何数。

答案的计算

首先我们要知道,这道题中建出来的图非常特殊。

考虑边的实际意义,就会发现如果(x)(y)有边,(y)(z)有边,则(x)(z)也必然有边。

因此对于这道题中的一个强连通分量,它实际上是一个团。

容易发现,对于一个团中的每对相反的有向边,给它强制定向(例如强制编号小的点向编号大的点连边),把整个团变成一个(DAG),在此题中实际上不会对答案造成任何影响。

而此时每个点能产生贡献,当且仅当能到达它的点(必然直接有边相连)都没有被选择,言下之意也就是其他的点都可以任选(假设有(t)个),那么这个点的贡献就是(2^t)

具体实现中只要知道边的存在情况,并不用真的建出图来。

代码:(O(sqrt p+n^2logp))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 5000
#define S 10000
#define X 998244353
using namespace std;
int n,p,pr,Phi,a[N+5],w[N+5],k[N+5],pt,pp[S+5];
I int gcd(CI x,CI y) {return y?gcd(y,x%y):x;}
I int QP(RI x,RI y,CI m) {RI t=1;W(y) y&1&&(t=1LL*t*x%m),x=1LL*x*x%m,y>>=1;return t;}
I bool IsP(CI x) {if(x==1) return 0;for(RI i=2;i*i<=x;++i) if(!(x%i)) return 0;return 1;}
I int Q(CI x)//对于a[x]求出最大的w[x]满足a[x]^(Phi/w[x])≡1
{
	if(x==1) return 0;RI i,t=1;//特判x=1
	for(i=1;i<=pt;++i) QP(x,Phi/(t*pp[i]),p)==1&&(t*=pp[i]);return t;//尝试乘上每个质因子
}
I bool Check(CI i,CI j)//判断是否存在i到j的有向边
{
	if(!k[i]&&!k[j]) return w[i]&&!(w[j]%w[i]);if(!k[i]||!k[j]) return false;//两个都为0;一个为0一个不为0
	return !(k[j]%k[i])&&QP(a[i],k[j]/k[i],p)==a[j];//两个都不为0,只有一种可能
}
int main()
{
	RI i,j;if(scanf("%d%d",&n,&p),IsP(p)) pr=p;else for(pr=2;p%pr;++pr);Phi=p/pr*(pr-1);//求出p是哪个质数的幂
	RI x=Phi;for(i=2;i*i<=x;++i) if(!(x%i)) W(!(x%i)) pp[++pt]=i,x/=i;x^1&&(pp[++pt]=x);//分解质因数,注意重复质因数要存多次
	for(i=1;i<=n;++i) if(scanf("%d",a+i),(x=gcd(a[i],p))==1) w[i]=Q(a[i]);else W(!(x%pr)) x/=pr,++k[i];//预处理每个数的信息
	RI t,ans=0;for(i=1;i<=n;ans=(ans+QP(2,t,X))%X,++i)//求出不能到点i的点数t
		for(t=0,j=1;j<=n;++j) i^j&&(!Check(j,i)||(i<j&&Check(i,j)))&&++t;//j不能到i,或i,j在团中且i的编号小于j的编号
	return printf("%d
",ans),0;
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu6730.html