前言
考试时看出来了有周期,答案是所有周期的最小公倍数+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
转化为(不足可用1补齐,即一个数自己对应自己,对lcm无影响),求lcm(n1,n2,...,nk)的种类数
一个数n可以表示成:(p1,p2...px为质数)
pi的指数会影响lcm的结果,所以通过指数求lcm的方案数
所以问题又转化为:枚举各质数pi的指数的情况,并使得
最后做法就是背包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;
}