BZOJ1025 [SCOI2009]游戏(置换+数论+背包)

题意真纠结


题目大意:

规定一种1~N的对应关系(1~N的一种排列),按这种关系将顺序序列1~N反复变换,变回1~N所经过的变换次数+1 记为这种对应关系的排数,求排数有多少个 


【题解】

置换群的问题。对于每种对应关系,将其分解为循环节的形式,则变换次数为:各循环节长度的最小公倍数 
如 对应关系:1->2 2->3 3->1 4->5 5->4 6->6,转化后就是(1 2 3)(4 5)(6),排数 = lcm(3,2,1)+1 = 7
循环节总长为N,所以问题转化为:
将N分解成若干正整数之和,求它们最小公倍数可能的取值个数 


我们发现,若一个分解出的数为a^p*b^q(a,b为质数),∵ a^p*b^q >= a^p+b^q ∴ 一定可以将这个数拆分为 a^p+b^q+x,lcm个数不会减少

因此,每个分解出的数都为 a^x (a为质数)不会漏解 


因此,问题转化为类似背包,满足 Ai = ai^k,sigma(Ai) <= N
设f[i][j]表示:用前i个质数组成j的方案数,那么f[i][j] = sigma( f[i-1][j-pri[i]^k] )

答案为 sigma(f[质数个数][0~N])

#include<stdio.h>
#include<stdlib.h>
long long f[505][1005]={0};
int pri[505]={0},hash[1005]={0};
int p=0;
void get_prime(int n)
{
	int i,j;
	for(i=2;i<=n;i++)
		if(hash[i]==0)
		{
			pri[++p]=i;
			for(j=i;j<=n;j+=i)
				hash[j]=1;
		}
}
int main()
{
	long long ans=0;
	int n,i,j,k;
	scanf("%d",&n);
	get_prime(n);
	f[0][0]=1;
	for(i=1;i<=p;i++)
	{
		for(j=0;j<=n;j++)
		{
			f[i][j]=f[i-1][j];
			for(k=pri[i];j-k>=0;k*=pri[i])
				f[i][j]+=f[i-1][j-k];
		}
	}
	for(i=0;i<=n;i++)
		ans+=f[p][i];
	printf("%lld",ans);
	return 0;
}


你可能感兴趣的:(数论,动态规划,置换,bzoj)