bzoj 4833: [Lydsy1704月赛]最小公倍佩尔数

前言

上午模拟赛考了这个题
虽然之前做过,但是忘得差不多了
最后一步什么构造函数的完全没印象,可能因为这玩意实在不常见
于是yy了半天,得到一个可能更为简单的做法,至少不需要构造函数,在这里记录一下
感觉很多yy出来的好东西都没有记录,然后忘了,实在可惜

题解

首先, l c m lcm lcm g c d gcd gcd方面的前置知识就不再赘述
这个方面似乎没有方法简化了
直接得到模型吧
就是 Π f ( g c d ( a i , a i + 1...... ) ) ( − 1 ) ∣ T + 1 ∣ \Pi f(gcd(a_i,a_{i+1......}))^{(-1)^{}|T+1|} Πf(gcd(ai,ai+1......))(1)T+1
不难想到一个暴力DP
f i , j f_{i,j} fi,j表示前i个数,当前的 g c d gcd gcd j j j的带系数的个数和
然后考虑一个 i i i在不在转移即可
但是这样是 O ( n 2 ) O(n^2) O(n2)

考虑 h ( i ) h(i) h(i)表示 [ 1 , i ] [1,i] [1,i]里面,枚举的子集当中 i i i必须选的积
那么 g i g_i gi显然就是 g i − 1 ∗ h i g_{i-1}*h_i gi1hi
问题就是 h ( i ) h(i) h(i)怎么求
我们对于一个子集,把他的贡献算在他最大的元素 j j j
( i , j ) = d (i,j)=d (i,j)=d
假如 j = d j=d j=d,那么其实加不加入i,对于这个集合的gcd没有任何影响,只不过指数的 T T T加了1
于是乘上 d ( j ) d(j) d(j)的倒数即可
否则,我们可以把 j j j的子集分为两类,一个是有 d d d的一个是没有 d d d的,显然这两类个数相同且互为倒数,因此答案是1,也就是不需要转移
于是得到 d d d的递推式
d ( i ) = Π j ∣ i 1 d ( j ) d(i)=\Pi_{j|i}\frac{1}{d(j)} d(i)=Πjid(j)1
特殊的,当 j = i j=i j=i的时候贡献就是 f ( i ) f(i) f(i)

然后就没有了
代码也很好写
CODE:

#include
#include
#include
#include
#include
using namespace std;
typedef long long LL;
const int N=1000005;
int MOD;
int add (int x,int y)   {x=x+y;return x>=MOD?x-MOD:x;}
int mul (int x,int y)   {return (LL)x*y%MOD;}
int dec (int x,int y)   {x=x-y;return x<0?x+MOD:x;}
int Pow (int x,int y)
{
	if (y==0) return 1;
	if (y==1) return x;
	int lalal=Pow(x,y>>1);
	lalal=mul(lalal,lalal);
	if (y&1) lalal=mul(lalal,x);
	return lalal;
}
int T;
int n;
int f[N];
int g[N];
int main()
{
	freopen("number.in","r",stdin);
	freopen("number.out","w",stdout);
	scanf("%d",&T);
	while (T--)
	{
		scanf("%d%d",&n,&MOD);
		f[1]=1;f[2]=2;for (int u=3;u<=n;u++) f[u]=add(f[u-2],add(f[u-1],f[u-1]));
		for (int u=1;u<=n;u++) g[u]=f[u];g[0]=1;
		int ans=0; 
		for (int u=1;u<=n;u++)
		{
			int Inv=Pow(g[u],MOD-2);
			for (int i=u+u;i<=n;i+=u) g[i]=mul(g[i],Inv);
			g[u]=mul(g[u],g[u-1]);
			ans=add(ans,mul(u,g[u]));
		}
		printf("%d\n",ans);
	}
	return 0;
}

你可能感兴趣的:(高二生活)