整个映射关系可以分解成几个循环(置换群的预备知识?),那么总行数就等于各个循环长度的最小公倍数+1(因为有个第一行的1~N)。那么有多少种可能的排数就等于问有多少种可能的最小公倍数。

  呃现在问题就变成了:给你一个数N,将它分解成几个数的和,然后找这些数的最小公倍数总共多少种。很明显又要找质数了>_>。

  可以发现只要找循环长度(即拆出来的数)是质数的幂的情况就可以了,因为像6=2*3这种情况,我们可以用2和3来代替,又由于对于正整数来说,和$\leq$积,所以所有的非质数幂的情况都可以用质数幂的情况来表示/代替。(取一个6等于取2和3)

  这个枚举总共有多少种分拆方案……我是用DP来实现的(没办法,dfs会TLE)

  令$f[i][j]$表示用前 i 种质数的幂拼出 j 的方案数,那么$ans=\sum_{j=1}^n f[tot][j]$ tot为小于等于n的质数的数量。

  转移也很简单啦~我的方法是从当前节点去更新其他节点的递推……写的可能有点奇怪……

  f[i][j]可以转移到:f[i+1][j]和f[i+1][j+k] $(k=prime[i+1]^t)$ 呃……好像说的不太清楚……看我代码吧>_<不过我开了滚动数组……

 1 /**************************************************************
 2     Problem: 1025
 3     User: Tunix
 4     Language: C++
 5     Result: Accepted
 6     Time:20 ms
 7     Memory:828 kb
 8 ****************************************************************/
 9  
10 //BZOJ 1025
11 #include<cstdio>
12 #include<algorithm>
13 #define F(i,j,n) for(int i=j;i<=n;++i)
14 using namespace std;
15 typedef long long LL;
16 const int N=1010;
17 int n,prime[N],tot;
18 LL f[2][N],ans;
19 bool vis[N];
20 void getprime(int n){
21     F(i,2,n){
22         if (!vis[i]) prime[++tot]=i;
23         F(j,1,tot){
24             if (i*prime[j]>n) break;
25             vis[i*prime[j]]=1;
26             if (i%prime[j]==0) break;
27         }
28     }
29 }
30 int main(){
31     scanf("%d",&n);
32     getprime(n);
33     f[0][0]=1;
34     for(int i=0;i<tot;i++){
35         int now=i&1;
36         F(j,0,n) f[now^1][j]=0;
37         F(j,0,n){
38             f[now^1][j]+=f[now][j];
39             for(int k=prime[i+1];k+j<=n;k*=prime[i+1])
40                 f[now^1][j+k]+=f[now][j];
41         }
42     }
43     F(j,1,n) ans+=f[tot&1][j];
44     printf("%lld\n",ans+1);
45     return 0;
46 }
View Code

相关文章: