把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷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;
}
posted @ 2021-01-11 19:12  TheLostWeak  阅读(172)  评论(0编辑  收藏  举报