【洛谷6730】[WC2020] 猜数游戏(数论)
- 给定一个长度为\(n\)的序列,其中每个数都在\([1,p)\)范围内,且各不相同。
- 规定当你选了序列中的某一个数\(x\),则所有\(x^i\%p\)都会被选中。
- 定义一个序列的代价为选中整个序列至少需要选的数的个数,求给定序列所有子序列的代价之和。
- \(n\le5\times10^3,p\le10^8\)且为某个质数的幂
建图:判断连边关系
考虑我们暴力建出一张有向图,如果选中了\(i\)能选中\(j\),就连一条从\(i\)到\(j\)的边。
设\(p\)是质数\(pr\)的幂,然后计算出\(a_i=v_i\times pr^{k_i},a_j=v_j\times 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\n",ans),0;
}