SCOI2009(HYSBZ1025)“游戏”

来源:互联网 发布:麻将游戏服务端源码 编辑:程序博客网 时间:2024/05/20 23:58

题目:HYSBZ - 1025

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
1 2 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想知道,对于所有可能的对应关系,有多种可能的排数。

分析:

首先,暴力枚举对应关系的复杂度为O(n!),明显与枚举对应关系的方法就是GG的,那我们还是来找规律吧!

这里的排数就是回到原状态的最小步数,做过类似的有关序列的对应关系变化的人应该知道,对于一整段序列回到原状态的最小步数应该是等于序列中每一个位置回到原数字的最小步数的lcm(最小公倍数),并且一段长为n的序列在不同的对应关系下需要的最小次数最多为n。

其实我们就可以把其看作每个子序列(这里的子序列是指回归原态步数相同的位置组成的子序列)回到原态的步数的lcm,如题目中每一个子序列(两子序列分别为1,2,3和4,5,6)回到原数字的最小步数为3,2,。所以答案为6。

再来看对应关系,其实不同的对应关系就是把咱们的原序列分成了不同的若干个子序列,那么我们的问题可以转化为:

给出一个数n,并有一段序列满足A1+A2+A3+……+Ak=n,求lcm(A1,A2,A3……Ak)有多少种情况。

对于这个我们可以用一个dp(记搜)来解决,我们先打一个素数表,设状态dp(i,j)为当前枚举到质数表中第i个数,且剩余枚举的数的总和为j,则有转移:

dp(i,j)=dp(i+1,j)+sigma{dp(i+1,j-prime[i]^k)}(prime[i]^k<=j)

(前者是不选这个质数,后者是选这个质数的几次方但不大于j)

话说这道题n<=1000,也不知道当时考试时有没有人暴力打表的。。。。

代码:

#include<cstdio>#include<algorithm>#include<cstring>using namespace std;long long f[1001][1001];int prime[1001],vis[1001];int n,cnt;void Prime(int n)//素数表{cnt=0;//cnt从零开始 memset(vis,0,sizeof(vis));for(int i=2;i<n;i++){if(!vis[i])prime[cnt++]=i;for(int j=0;j<cnt&&i*prime[j]<n;j++)//prime数组从零开始 {vis[i*prime[j]]=1;if(i%prime[j]==0)break;}}cnt--;return ; }long long dp(int id,int rest){if(id<0) return 1ll;if(f[id][rest]) return f[id][rest];f[id][rest]+=dp(id-1,rest);for(int tmp=prime[id];tmp<=rest;tmp*=prime[id])f[id][rest]+=dp(id-1,rest-tmp);return f[id][rest];}int main(){scanf("%d",&n);Prime(n);printf("%lld",dp(cnt,n));//笔者偷懒,逆着枚举i return 0;}