【DP-质数】中山纪念中学暑期游Day9——游戏

前言

考试时看出来了有周期,答案是所有周期的最小公倍数+1,然而还是选择了把握较高的暴力

好多博客写的很简略,自己搞了半天才搞懂,自力更生写了个详细版本qwq!

题目

Time Limits: 1000 ms  Memory Limits: 65536 KB

windy学会了一种游戏。 对于1到N这N个数字,都有唯一且不同的1到N的数字与之对应。 最开始windy把数字按顺序1,2,3,……,N写一排在纸上。 然后再在这一排下面写上它们对应的数字。 然后又在新的一排下面写上它们对应的数字。 如此反复,直到序列再次变为1,2,3,……,N。 如: 1 2 3 4 5 6 对应的关系为 1->2 2->3 3->1 4->5 5->4 6->6 windy的操作如下

1 2 3 4 5 6

2 3 1 5 4 6

3 1 2 4 5 6

12 3 5 4 6

2 3 1 4 5 6

3 1 2 5 4 6

1 2 3 4 5 6

这时,我们就有若干排1到N的排列,上例中有7排。 现在windy想知道,对于所有可能的对应关系,有多少种可能的排数。

Input

一个整数,N。

Output

一个整数,可能的排数。

Sample Input

3

Sample Output

3

【数据范围】

100%的数据,满足 1 <= N <= 1000 

分析

发现这些数的对应关系能构成很多个周期,答案就是这些周期的最小公倍数(lcm)+1

转化为n_{1}+n_{2}+...+n_{k}\leq n(不足可用1补齐,即一个数自己对应自己,对lcm无影响),求lcm(n1,n2,...,nk)的种类数

一个数n可以表示成:p1^{k1}+p2^{k2}+...+px^{k2}(p1,p2...px为质数)

lcm(p1,p2,...,px)=p1^{max(k1)}*p2^{max(k2)}*...*px^{max(kx)}

pi的指数会影响lcm的结果,所以通过指数求lcm的方案数

所以问题又转化为:枚举各质数pi的指数的情况,并使得p1^{k1}+p2^{k2}+...+px^{k2}\leq n

最后做法就是背包dp,dp[ i ][ j ]:前i个质数的幂相加构成数j的方案数 

质数用质数筛法处理出来

考试暴搜代码

#include<cstdio>
#include<vector>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN=1000;
bool vis[MAXN+5],v[MAXN*MAXN*10+5];
int a[MAXN+5],f[MAXN+5],tmp[MAXN+5]; 
int n,ans;
vector<int> num;
bool Check(int a[],int b[])
{
	bool flag=true;
	for(int i=1;i<=n;i++)
		if(a[i]!=b[i])
		{
			flag=false;
			break;
		}
	return flag;
}
void Solve()
{
	ans=2;
	for(int i=1;i<=n;i++)
		tmp[i]=f[i];
	while(!Check(a,tmp))
	{
		for(int i=1;i<=n;i++)
			tmp[i]=f[tmp[i]];
		ans++;
		if(ans>10)
			break;
	}
	num.push_back(ans);
}
void dfs(int id,int num)
{
	f[id]=num;
	if(id==n)
	{
		Solve();
		return ;
	}
	for(int i=1;i<=n;i++)
		if(!vis[i])
		{
			vis[i]=1;
			dfs(id+1,i);
			vis[i]=0;
		}
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		a[i]=i;
	for(int i=1;i<=n;i++)
	{
		vis[i]=1;
		dfs(1,i);
		vis[i]=0;
	}
	ans=0;
	for(int i=0;i<num.size();i++)
		if(!v[num[i]])
		{
			v[num[i]]=1;
			ans++;
		}
	printf("%d",ans);
	return 0;
}

考试猜规律代码

#include<cstdio>
#include<vector>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN=1000;
ll ans[MAXN+5];
int n;
void Prepare()
{
	for(int i=1;i<=4;i++)
		ans[i]=i;
	ans[5]=6,ans[6]=6,ans[7]=8,ans[8]=9;
	ans[9]=10,ans[10]=10,ans[11]=10;
	int cnt1=0,cnt2=0;
	for(int i=1;i<=n+5;i++)
	{
		if(i%2==0)
		{
			cnt1++;
			if(cnt1==4)
			{
				cnt1=0;
				if(i>11)
					ans[i]=i+1;
			}
			else
				ans[i]=i;
		}
		else
		{
			cnt2++;
			if(cnt2==1||cnt2==2)
				if(i>11)
					ans[i]=i;
			else if(cnt2==3&&i>11)
				ans[i]=i+1;
			else if(cnt2==4)
			{
				if(i>11)
					ans[i]=i+1;
				cnt2=0;
			}
		}
	}
}
int main()
{
	scanf("%d",&n);
	Prepare();
	printf("%lld",ans[n]);
	return 0;
}
//瞎搞...没看出规律orz... 

AC代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN=1e3;
ll dp[MAXN+5][MAXN+5];
//dp[i][j]:前i个质数构成数j的方案数 
int vis[MAXN+5],prime[MAXN+5];
int tot,n;
ll ans;
void Sieve()//线性欧拉筛 
{
	for(int i=2;i<=n;i++)
	{
		if(!vis[i])
			prime[++tot]=i;
		for(int j=1;j<=tot;j++)
		{
			if(i*prime[j]>n)
				break;
			vis[i*prime[j]]=1;
			if(i%prime[j]==0)
				break;
		}
	}
}
void Solve()//dp背包转移 
{
	dp[0][0]=1;//初始化 
	for(int i=1;i<=tot;i++)//前i个质数 
	{
		for(int j=0;j<=n;j++)
		{
			dp[i][j]=dp[i-1][j];
			for(int k=prime[i];k<=j;k*=prime[i])
				dp[i][j]+=dp[i-1][j-k]; 
		} 
	}
}
int main()
{
	scanf("%d",&n);
	Sieve();
	Solve();
	for(int i=0;i<=n;i++)//周期之和<=n的都是答案 
		ans+=dp[tot][i];
	printf("%lld",ans);
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值