【BZOJ3202】项链(SDOI2013)-数论+组合数学综合

测试地址:项链
题目大意: n ( ≤ 1 0 14 ) n(\le 10^{14}) n(1014)个珠子串成一个环形项链,每个珠子是正三棱柱,每一面上写一个 1 1 1 ~ a ( ≤ 1 0 7 ) a(\le 10^7) a(107)中的数字,当且仅当写的三个数字 gcd ⁡ \gcd gcd 1 1 1时,这个珠子是合法的珠子。两个珠子旋转或翻转后相同就看做相同。用 n n n个合法的珠子串成一个环形项链,要求相邻两个珠子不同,求本质不同的方案数,两个项链经旋转后相同就看做相同。
做法: 本题是一道数论+组合数学集大成题,需要用到的比较重要的知识有:Burnside引理,莫比乌斯反演,质因数分解及大数因数枚举,欧拉函数,数列通项公式推导。
很明显,我们应该先算出珠子的种类数 m m m。用Burnside引理分析,置换群中应该有 6 6 6个置换,即置换到 1 1 1 ~ 3 3 3的全排列,现在分析每个置换的不动点数目(即经过置换后不变的方案数)。
首先,有一个置换是不动的,那么不动点数应该是 ∑ i = 1 a ∑ j = 1 a ∑ k = 1 a [ gcd ⁡ ( i , j , k ) = 1 ] \sum_{i=1}^a\sum_{j=1}^a\sum_{k=1}^a[\gcd(i,j,k)=1] i=1aj=1ak=1a[gcd(i,j,k)=1]
接着,有两个置换中,是长度为 3 3 3的循环,那么当且仅当三个数都相等,且 gcd ⁡ \gcd gcd等于 1 1 1时满足条件,因此显然对每种置换只有 1 1 1个不动点( { 1 , 1 , 1 } \{1,1,1\} {1,1,1}),加起来就是 2 ⋅ 1 2\cdot 1 21
最后,有三个置换中,是一个长度为 2 2 2的循环加一个点,那么那个循环中的两个点应该为同一个数,两个相同的数的 gcd ⁡ \gcd gcd就是本身,所以不动点数实际上就是两个数 gcd ⁡ \gcd gcd 1 1 1的方案数,即 ∑ i = 1 a ∑ j = 1 a [ g c d ( i , j ) = 1 ] \sum_{i=1}^a\sum_{j=1}^a[gcd(i,j)=1] i=1aj=1a[gcd(i,j)=1],加起来就是上面那个式子乘 3 3 3
那么合法的珠子种类数就是上面那些东西加起来再乘上 1 6 \frac{1}{6} 61了。而上面的两个和式,都可以用基础的莫比乌斯反演知识推出来一个可以数论分块的式子,只需要先线性筛求出莫比乌斯函数 μ \mu μ的前缀和即可。
现在有了 m m m,就可以讨论第二个问题了:如何求本质不同的项链数。还是用Burnside引理分析,我们发现一个项链旋转 i i i个单位后,置换中形成循环节长度为 n gcd ⁡ ( n , i ) \frac{n}{\gcd(n,i)} gcd(n,i)n的很多循环。具体到环上,我们发现这要求的就是,用 m m m种颜色染每个点,相邻两个点颜色不同,且染色序列形成长为 gcd ⁡ ( n , i ) \gcd(n,i) gcd(n,i)的循环节的方案数。因为是循环,所以上面要求的就是用 m m m中颜色染一个长为 gcd ⁡ ( n , i ) \gcd(n,i) gcd(n,i)的环,相邻两个点颜色不同的方案数,我们把这记作 f ( gcd ⁡ ( n , i ) ) f(\gcd(n,i)) f(gcd(n,i))
f ( n ) f(n) f(n)是一个经典的组合数学问题,我还在机房大佬的《高考数学你掌握了吗?》书里看到了这个题…证法比较神,虽然证明很经典,传播范围很广,但我在这里还是写一下,不想看的可以直接跳过。
我们来看,如果用 m m m种颜色染一条长度为 n n n,方案数显然为 m ( m − 1 ) n − 1 m(m-1)^{n-1} m(m1)n1。现在要拿出一个神奇的结论: m ( m − 1 ) n − 1 = f ( n ) + f ( n − 1 ) m(m-1)^{n-1}=f(n)+f(n-1) m(m1)n1=f(n)+f(n1)。为什么呢?如果令链的两端颜色不同,那么显然可以接起来成为环,方案数为 f ( n ) f(n) f(n),否则如果令链的两端颜色相同,那么可以把两端缩成一个点再接成环,方案数为 f ( n − 1 ) f(n-1) f(n1)。因此我们得到递推公式:
f ( n ) = m ( m − 1 ) n − 1 − f ( n − 1 ) f(n)=m(m-1)^{n-1}-f(n-1) f(n)=m(m1)n1f(n1)
两边同除 ( m − 1 ) n (m-1)^n (m1)n得:
f ( n ) ( m − 1 ) n = m m − 1 − 1 m − 1 ⋅ f ( n − 1 ) ( m − 1 ) n − 1 \frac{f(n)}{(m-1)^n}=\frac{m}{m-1}-\frac{1}{m-1}\cdot \frac{f(n-1)}{(m-1)^{n-1}} (m1)nf(n)=m1mm11(m1)n1f(n1)
g ( n ) = f ( n ) ( m − 1 ) n g(n)=\frac{f(n)}{(m-1)^n} g(n)=(m1)nf(n),有:
g ( n ) = − 1 m − 1 ⋅ g ( n − 1 ) + m m − 1 g(n)=-\frac{1}{m-1}\cdot g(n-1)+\frac{m}{m-1} g(n)=m11g(n1)+m1m
解这样的数列的通项公式就是高考难度的了(虽然还是让不懂的我非常难过),即如果有:
g ( n ) = A ⋅ g ( n − 1 ) + B g(n)=A\cdot g(n-1)+B g(n)=Ag(n1)+B
那么令 h ( n ) = g ( n ) + x h(n)=g(n)+x h(n)=g(n)+x,构造 h h h为等比数列,则有:
( g ( n ) + x ) = q ( g ( n − 1 ) + x ) (g(n)+x)=q(g(n-1)+x) (g(n)+x)=q(g(n1)+x)
将这个式子和上面的式子对照,可得当 x = B A − 1 x=\frac{B}{A-1} x=A1B时, h h h就为等比数列。
在上面那个式子中算出 x = − 1 x=-1 x=1,因此 h ( n ) = g ( n ) − 1 h(n)=g(n)-1 h(n)=g(n)1是首项为 − 1 -1 1,公比为 − 1 m − 1 -\frac{1}{m-1} m11的等比数列,那么算出 h ( n ) h(n) h(n)的通项为:
h ( n ) = ( − 1 ) n ⋅ 1 ( m − 1 ) n − 1 h(n)=(-1)^n\cdot \frac{1}{(m-1)^{n-1}} h(n)=(1)n(m1)n11
于是 g ( n ) = h ( n ) + 1 = ( − 1 ) n ⋅ 1 ( m − 1 ) n − 1 + 1 g(n)=h(n)+1=(-1)^n\cdot \frac{1}{(m-1)^{n-1}}+1 g(n)=h(n)+1=(1)n(m1)n11+1,因此 f ( n ) = g ( n ) ⋅ ( m − 1 ) n = ( m − 1 ) n + ( − 1 ) n ⋅ ( m − 1 ) f(n)=g(n)\cdot (m-1)^n=(m-1)^n+(-1)^n\cdot (m-1) f(n)=g(n)(m1)n=(m1)n+(1)n(m1)
于是最后我们得到了 f ( n ) = ( m − 1 ) n + ( − 1 ) n ⋅ ( m − 1 ) f(n)=(m-1)^n+(-1)^n\cdot (m-1) f(n)=(m1)n+(1)n(m1)这个重要的结论。最后我们只要想办法求出答案即可:
a n s = 1 n ∑ i = 1 n f ( gcd ⁡ ( n , i ) ) ans=\frac{1}{n}\sum_{i=1}^nf(\gcd(n,i)) ans=n1i=1nf(gcd(n,i))
n n n很大,不能直接计算。根据数论直觉,我们想到应该枚举 d = gcd ⁡ ( n , i ) d=\gcd(n,i) d=gcd(n,i),那么使得 n n n i i i gcd ⁡ \gcd gcd d d d i i i的数量有多少个呢?两边除 d d d,其实要求的就是与 n d \frac{n}{d} dn互质的比它小的数有多少个,这显然等于欧拉函数 φ ( n d ) \varphi(\frac{n}{d}) φ(dn)。因此上式可以化为:
a n s = 1 n ∑ d ∣ n φ ( n d ) ⋅ f ( d ) ans=\frac{1}{n}\sum_{d|n}\varphi(\frac{n}{d})\cdot f(d) ans=n1dnφ(dn)f(d)
n n n的因数的个数就比 n n n小很多了,可以通过质因数分解+搜索的方式枚举出来,实测不会爆。而因为 n n n的质因数分解中至多有一个 > n >\sqrt n >n 的质因数,所以我们只需要处理出 n \sqrt n n 内的质数表即可,这可以在一开始求莫比乌斯函数的线性筛中同时求出。至于欧拉函数,也可以在枚举因数的同时求出。
于是经过漫长的讨论,这一题总算是解决了…吗?注意到一个问题, n n n很大,以至于它有可能是模数 1 0 9 + 7 10^9+7 109+7的倍数,那么我们就不能直接对 n n n求逆元了。解决的办法是,将上述算法中的模数改为 ( 1 0 9 + 7 ) 2 (10^9+7)^2 (109+7)2,那么如果 n n n 1 0 9 + 7 10^9+7 109+7的倍数,则上面算出的答案对这个新模数取模后应该也是 1 0 9 + 7 10^9+7 109+7的倍数,因此把 n n n和算出的答案同时除 m o d mod mod,再对 n n n求关于 m o d mod mod的逆元即可。而对于其他情况,直接将算出的答案再对 m o d mod mod取模进行计算就行了。至于会乘爆的问题,建议使用一种叫 O ( 1 ) O(1) O(1)快速乘的奇葩操作,可以保证两个long long范围内相乘取模的结果不错,可以参看我代码中的mult()函数。这样我们就解决了这一题。
我傻逼的地方:在调用mult()函数前,如果两个数没有先取模,还是有可能乘爆…这样就炸了一个点。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1000000007;
const ll MOD=mod*mod;
const ll inv6=(5ll*MOD+1ll)/6ll;
int T;
ll maxn=0,n[15],x[15],a[15],m,mu[10000010],prime[10000010],totans;
vector<int> p[15];
bool vis[10000010]={0};

ll add(ll x,ll y,ll mod)
{
	return ((x+y)%mod+mod)%mod;
}

ll mult(ll x,ll y,ll mod)
{
	x%=mod,y%=mod;
	return (x*y-(ll)(((long double)x*y+0.5)/(long double)mod)*mod+mod)%mod;
}

ll power(ll a,ll b,ll mod)
{
	ll s=1,ss=a;
	while(b)
	{
		if (b&1) s=mult(s,ss,mod);
		ss=mult(ss,ss,mod);
		b>>=1;
	}
	return s;
}

void calc_prime(int limit)
{
	mu[1]=1;
	for(int i=2;i<=limit;i++)
	{
		if (!vis[i])
		{
			mu[i]=-1;
			prime[++prime[0]]=i;
			for(int j=1;j<=T;j++)
				if (n[j]%i==0)
				{
					p[j].push_back(i);
					while(x[j]%i==0) x[j]/=i;
				}
		}
		for(int j=1;j<=prime[0]&&i*prime[j]<=limit;j++)
		{
			vis[i*prime[j]]=1;
			if (i%prime[j]==0)
			{
				mu[i*prime[j]]=0;
				break;
			}
			mu[i*prime[j]]=-mu[i];
		}
	}
	for(int i=2;i<=limit;i++)
		mu[i]=(mu[i]+mu[i-1]+MOD)%MOD;
}

ll solve(ll n,bool type)
{
	ll ans=0;
	for(ll i=n;i;i=n/(n/i+1ll))
	{
		int l=n/(n/i+1ll)+1ll,r=i;
		if (type) ans=add(ans,mult((mu[r]-mu[l-1]+MOD)%MOD,mult(n/i,mult(n/i,n/i,MOD),MOD),MOD),MOD);
		else ans=add(ans,mult((mu[r]-mu[l-1]+MOD)%MOD,mult(n/i,n/i,MOD),MOD),MOD);
	}
	return ans;
}

ll f(ll n)
{
	ll ans=power((m-1ll+MOD)%MOD,n,MOD);
	ans=add(ans,mult((n%2)?(MOD-1ll):1ll,(m-1ll+MOD)%MOD,MOD),MOD);
	return ans;
}

ll dfssolve(ll now,ll nowphi,int id)
{
	return mult(nowphi,f(n[id]/now),MOD);
}

void dfs(ll now,ll nowphi,int id,int step,int num)
{
	if (step>=p[id].size()) return;
	if (step==0&&num==0)
		totans=add(totans,dfssolve(now,nowphi,id),MOD);
	if (num) totans=add(totans,dfssolve(now,nowphi,id),MOD);
	if (n[id]%(now*p[id][step])==0)
	{
		if (!num) dfs(now*p[id][step],nowphi*(p[id][step]-1ll),id,step,num+1);
		else dfs(now*p[id][step],nowphi*p[id][step],id,step,num+1);
	}
	dfs(now,nowphi,id,step+1,0);
}

int main()
{
	scanf("%d",&T);
	for(int i=1;i<=T;i++)
	{
		scanf("%lld%lld",&n[i],&a[i]);
		maxn=max(maxn,(ll)sqrt(n[i])+1ll);
		maxn=max(maxn,a[i]);
		x[i]=n[i];
	}
	
	calc_prime(maxn);
	for(int i=1;i<=T;i++)
	{
		m=mult(add(solve(a[i],1),mult(solve(a[i],0),3ll,MOD)+2ll,MOD),inv6,MOD);
		
		if (x[i]>1) p[i].push_back(x[i]);
		
		totans=0;
		dfs(1ll,1ll,i,0,0);
		if (n[i]%mod==0) printf("%lld\n",totans/mod*power(n[i]/mod,mod-2,mod)%mod);
		else printf("%lld\n",totans%mod*power(n[i],mod-2,mod)%mod);
	}
	
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值