WC2020 猜数游戏

题目描述

LOJ3330

题目分析

若存在 k k k 使得 a k ≡ b ( m o d p ) a^k\equiv b\pmod p akb(modp),则 a a a b b b 连一条边。

那么一个点在一个集合里面要被选的条件就是集合中不存在另一个点能够到达它(把环缩成点,每个环要被选的方案数是 2 环 大 小 − 1 2^{环大小}-1 21),那么所有方案选择的点数之和就是 ∑ 某 个 环 2 n − ( 可 到 达 这 个 环 的 点 数 ) ∗ ( 2 环 大 小 − 1 ) \sum_{某个环} 2^{n-(可到达这个环的点数)}*(2^{环大小}-1) 2n()(21)

暴力连边是 O ( n p ) O(np) O(np) 的,容易想到用原根优化。

如果 p p p 是奇素数,那么存在 k k k 使得 g a k ≡ g b ( m o d p ) g^{ak}\equiv g^b\pmod p gakgb(modp) 等价于存在 k k k 使得 a k ≡ b ( m o d φ ( p ) ) ak\equiv b\pmod {\varphi(p)} akb(modφ(p)),即 g c d ( a , φ ( p ) )   ∣   b gcd(a,\varphi(p))~|~b gcd(a,φ(p))  b
用 BSGS 求指标,复杂度就是 O ( n p + n 2 ) O(n\sqrt p+n^2) O(np +n2)

p = q k p=q^k p=qk 时,对于 ( a , q ) = 1 (a,q)=1 (a,q)=1 的可以按上面的方法做,而对于 a q b aq^b aqb,它们的连边形成了一个 DAG,自乘 log 次后就会变成 0,不会成环,可以直接暴力做,复杂度 O ( n log ⁡ p ) O(n\log p) O(nlogp)

p p p为奇素数的算法还可以进一步优化,条件可以转化为 g c d ( x , φ ( p ) )   ∣   g c d ( y , φ ( p ) ) gcd(x,\varphi(p))~|~gcd(y,\varphi(p)) gcd(x,φ(p))  gcd(y,φ(p)),而对于 g x ≡ a g^x\equiv a gxa,有 ord ( a ) = φ ( p ) g c d ( x , φ ( p ) ) \text{ord}(a)=\frac {\varphi(p)}{gcd(x,\varphi(p))} ord(a)=gcd(x,φ(p))φ(p) ord(a) \text{ord(a)} ord(a) a a a 的阶,即使得 a k ≡ 1 ( m o d p ) a^k\equiv 1\pmod p ak1(modp) 的最小的 k k k,即使得 x k ≡ 0 ( m o d φ ( p ) ) xk\equiv 0\pmod {\varphi(p)} xk0(modφ(p)) 的最小的 k k k

于是只需要求出 ord ( a ) \text{ord}(a) ord(a) 即可,因为 ord ( a )   ∣   φ ( p ) \text{ord}(a)~|~\varphi(p) ord(a)  φ(p),可以用后者的质因子试除,复杂度 O ( n log ⁡ 2 p ) O(n\log^2p) O(nlog2p)

Code:

#include<bits/stdc++.h>
#define maxn 5005
using namespace std;
const int mod = 998244353;
int n,p,q,phi,d[35],pw[maxn],ans;
int v1[maxn],s1,v2[maxn],s2,cnt[maxn];
map<int,int>id1,id2;
void Divide(){
	for(int i=2;i*i<=q;i++) if(q%i==0) {p=i;break;}
	if(!p) p=q;
	int x=phi=q-q/p;
	for(int i=2;i*i<=x;i++) if(x%i==0) {d[++*d]=i; while(x%i==0) x/=i;}
	if(x>1) d[++*d]=x;
}
int Pow(int a,int b,int mod){int s=1;for(;b;b>>=1,a=1ll*a*a%mod) b&1&&(s=1ll*s*a%mod);return s;}
int ord(int x){
	int w=phi;
	for(int i=1;i<=*d;i++)
		while(w%d[i]==0&&Pow(x,w/d[i],q)==1) w/=d[i];
	return w;
}
int main()
{
	scanf("%d%d",&n,&q),Divide();
	for(int i=pw[0]=1;i<=n;i++) pw[i]=pw[i-1]*2%mod;
	for(int i=1,x;i<=n;i++){
		scanf("%d",&x);
		if(x%p==0) v2[++s2]=x,id2[x]=s2;
		else{
			x=ord(x);
			if(!id1[x]) id1[x]=++s1,v1[s1]=x;
			cnt[id1[x]]++;
		}
	}
	for(int i=1;i<=s1;i++){
		int sum=n;
		for(int j=1;j<=s1;j++) if(v1[j]%v1[i]==0) sum-=cnt[j];
		ans=(ans+1ll*pw[sum]*(pw[cnt[i]]-1))%mod;
	}
	for(int i=1;i<=s2;i++) cnt[i]=n;
	for(int i=1;i<=s2;i++)
		for(int x=v2[i];x;x=1ll*x*v2[i]%q)
			cnt[id2[x]]--;
	for(int i=1;i<=s2;i++) ans=(ans+pw[cnt[i]])%mod;
	printf("%d\n",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值