2018.11.17【NOIP练习】zxyoi的项链(数学推理)(线性筛)(欧拉函数)(链表)(Polya原理)

传送门


解析:

zxyoi完成oi生涯中的第一次出题。

思路:

首先肯定是要推结论的。

令询问为 n n n m m m时的答案为 N ( n , m ) N(n,m) N(n,m)
首先断环成链,同一条项链考虑 m m m种断开的方式,那么得到的链的数量就有 m N ( n , m ) mN(n,m) mN(n,m)。每个位置任意填得到的答案为 n m n^m nm,其中必然有重复。

考虑序列 a 1 a 2 . . . a m a_1a_2...a_m a1a2...am会出现多少次,显然就是产生的循环位移 a k . . a m a 1 . . . a k − 1 a_k..a_ma_1...a_{k-1} ak..ama1...ak1有多少与原串相同。
S n S_n Sn表示颜色集合,则有 m N ( n , m ) = ∑ k = 1 m ∑ a 1 , a 2 , a 3 . . . a m ∈ S n [ a 1 a 2 . . . a m = a k . . . a m a 1 . . . a k − 1 ] mN(n,m)=\sum_{k=1}^{m}\sum_{a_1,a_2,a_3...a_m\in S_n}[a_1a_2...a_m=a_k...a_ma_1...a_{k-1}] mN(n,m)=k=1ma1,a2,a3...amSn[a1a2...am=ak...ama1...ak1]

然后就很妙了。考虑当 m = 6 m=6 m=6 k = 4 k=4 k=4的时候,如何计算 a 1 a 2 a 3 a 4 a 5 a 6 = a 4 a 5 a 6 a 1 a 2 a 3 a_1a_2a_3a_4a_5a_6=a_4a_5a_6a_1a_2a_3 a1a2a3a4a5a6=a4a5a6a1a2a3有多少组解?

显然 a 1 = a 4 , a 2 = a 5 , a 3 = a 6 a_1=a_4,a_2=a_5,a_3=a_6 a1=a4,a2=a5,a3=a6,所以实际上能够随意填的只有 3 = g c d ( m , k − 1 ) 3=gcd(m,k-1) 3=gcd(m,k1)个位置,解的数量为 n g c d ( m , k − 1 ) n^{gcd(m,k-1)} ngcd(m,k1)

严格的叙述为,考虑 ( m , k ) (m,k) (m,k)的解的限制。无非就是 a j = a ( j + k − 1 ) % m + 1 a_j=a_{(j+k-1)\%m+1} aj=a(j+k1)%m+1

d = g c d ( m , k − 1 ) d=gcd(m,k-1) d=gcd(m,k1),则所有的 a ( t + s × d ) % m + 1 a_{(t+s\times d)\%m+1} a(t+s×d)%m+1都是被锁死在了一起,能够随意选择的只有 a 1 , a 2 . . . a d a_1,a_2...a_d a1,a2...ad一共 d d d个数,则解的数量为 n g c d ( m , k − 1 ) n^{gcd(m,k-1)} ngcd(m,k1)

以上,我们证明了 m N ( n , m ) = ∑ k = 1 m n g c d ( m , k − 1 ) = ∑ k = 0 m − 1 n g c d ( m , k ) mN(n,m)=\sum_{k=1}^{m}n^{gcd(m,k-1)}=\sum_{k=0}^{m-1}n^{gcd(m,k)} mN(n,m)=k=1mngcd(m,k1)=k=0m1ngcd(m,k)

但是这个式子还需要化简。实测没化简过不了。

N ( n , m ) = 1 m ∑ d ∣ m n d ∑ k = 0 m − 1 [ g c d ( k , m ) = d ] N(n,m)=\frac{1}{m}\sum_{d|m}n^d\sum_{k=0}^{m-1}{[gcd(k,m)=d]} N(n,m)=m1dmndk=0m1[gcd(k,m)=d] = 1 m ∑ d ∣ m n d ∑ k = 0 m − 1 [ g c d ( k / d , m / d ) = 1 ] =\frac{1}{m}\sum_{d|m}n^d\sum_{k=0}^{m-1}[gcd(k/d,m/d)=1] =m1dmndk=0m1[gcd(k/d,m/d)=1] = 1 m ∑ d ∣ m n d ∑ k = 0 m / d − 1 [ g c d ( k , m / d ) = 1 ] =\frac{1}{m}\sum_{d|m}n^d\sum_{k=0}^{m/d-1}[gcd(k,m/d)=1] =m1dmndk=0m/d1[gcd(k,m/d)=1]

所以最终得到的式子为 N ( n , m ) = 1 m ∑ d ∣ m n d φ ( m / d ) N(n,m)=\frac{1}{m}\sum_{d|m}n^d\varphi(m/d) N(n,m)=m1dmndφ(m/d)

线性筛同时处理欧拉函数和因数表就行了。

这里因数表的处理有很多方法,我没有刻意去卡其他做法,询问数可以再大一点,可能就需要用这种方法了。

我这里用的是类似链表的方式,可以在 O ( ∑ σ 0 ( i ) ) O(\sum\sigma_0(i)) O(σ0(i))的复杂度内处理出所有因数表。其中 σ 0 ( i ) \sigma_0(i) σ0(i)表示 i i i的因数个数。

质数显然直接存就行了。

一个合数 B B B必然由质数 P P P和另一个数 A A A相乘得到,那么 A A A的所有因子必然都是 B B B的因子,那么 B B B的表末端直接连在 A A A的表末端就好了。然后考虑每个 A A A的因子 δ \delta δ,首先 δ × P \delta\times P δ×P必然是 B B B的因子,但是也有可能是 A A A的因子,这个直接判断一下再插入 B B B的链表末端就行了。

这样大部分因数表是由相当一部分数共享的,可以优化空间。


U P D UPD UPD

抱歉,这是一道Polya的裸题


代码(已删去所有常数优化,可过):

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define cs const

cs int P=500005;
cs int mod=1000000007;

int prime[P],pcnt;
bool mark[P];
vector<int> factor;
vector<int> pre;
int last[P],phi[P],inv[P];

void _copy(int goal,int f,int p){
	bool flag=false;
	for(int i=last[f];~i;i=pre[i]){
		int v=factor[i];
		if(f%(v*p)){
			if(flag)
			pre.push_back(factor.size()-1); 
			else pre.push_back(last[f]),flag=true;
			factor.push_back(v*p);
		}
	}
	last[goal]=pre.size()-1;
}

void linear_sieves(int len=P-5){
	last[1]=0;phi[1]=1;
	factor.push_back(1);
	pre.push_back(-1);
	for(int i=2;i<=len;++i){
		if(!mark[i]){
			prime[++pcnt]=i;
			phi[i]=i-1;
			_copy(i,1,i);
		}
		for(int j=1;j<=pcnt&&i*prime[j]<=len;++j){
			mark[i*prime[j]]=true;
			if(i%prime[j]==0){
				phi[i*prime[j]]=phi[i]*prime[j];
				_copy(i*prime[j],i,prime[j]);
				break;
			}
			phi[i*prime[j]]=phi[i]*(prime[j]-1);
			_copy(i*prime[j],i,prime[j]);
		}
	}
}

int quickpow(int a,int b){
	int ans=1;
	while(b){
		if(b&1)ans=(ll)ans*a%mod;
		b>>=1;
		a=(ll)a*a%mod;
	}
	return ans;
}

int query(int n,int m){
	int ans=0;
	for(int i=last[m];~i;i=pre[i]){
		ans=((ll)ans+(ll)quickpow(n,factor[i])*phi[m/factor[i]])%mod;
	}
	return (ll)ans*inv[m]%mod;
}

int T;
signed main(){
	linear_sieves();
	inv[1]=inv[0]=1;
	for(int i=2;i<=P-5;++i){
		inv[i]=(ll)(mod-mod/i)*inv[mod%i]%mod;
	}
	scanf("%d",&T);
	while(T--){
		int n,m;
		scanf("%d%d",&n,&m); 
		printf("%d\n",query(n,m));
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值