题目

题目链接:https://www.luogu.com.cn/problem/P2231
题面没有 LaTeX 太丑了,所以直接上题意简述吧。
给出 \(n,m\),求有多少个长度为 \(n\) 的序列,满足序列中所有数均为 \([1,m]\) 的整数且数列的 \(\gcd\)\(m\) 互质。

思路

\(m\) 视作数列第 \(n+1\) 项,问题转化为有多少个长度为 \(n+1\) 且最后一项为 \(m\) 的数列的 \(\gcd\)\(1\)
\(f[i]\) 表示整个数列的 \(\gcd=i\) 的方案数。因为整个数列与 \(m\)\(\gcd\) 一定是 \(m\) 的因子,所以 \(f[i]\) 最多只有 \(2\sqrt{m}\) 个位置有元素,所以直接拿一个 unorder_map 存即可。
可以发现 \(f[i]=\gcd\)\(i\) 的倍数的方案数 \(-\sum_{i|j,j\leq m} f[j]\)。而前面的那一部分显然等于 \(\lfloor \frac{m}{i} \rfloor^n\),所以我们可以从大到小枚举 \(m\) 的因子,计算出前面部分,然后用这个因子去贡献它的因子。最终答案即为 \(f[1]\)
时间复杂度 \(O(m)\)

代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

int n,m;
unordered_map<int,ll> f;

ll fpow(ll x,ll k)
{
	ll ans=1;
	for (;k;k>>=1,x=x*x)
		if (k&1) ans=ans*x;
	return ans;
}

void calc(int x)
{
	f[x]+=fpow(m/x,n);
	for (int i=1;i*i<=x;i++)
		if (x%i==0)
		{
			if (i!=x) f[i]-=f[x];
			if (i*i!=x && i!=1) f[x/i]-=f[x];
		}
}

int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i*i<=m;i++)
		if (m%i==0) calc(m/i);
	for (int i=sqrt(m-1);i>=1;i--)
		if (m%i==0) calc(i);
	printf("%lld",f[1]);
	return 0;
}