【SCOI2009】游戏

206 篇文章 0 订阅
119 篇文章 0 订阅

Description
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

Data Constraint

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

.
.
.
.
.
.
分析
1、把原序列按照转化关系分组,由题面可以发现(1,2,3)在三行之后回到原状态,而(4,5)需要四行,(6)需要一行,得到小结论:由n个数构成的相互转化关系,其对应序列需要n行回到原状态

2、如何确定一个数经过转化后能够回到原数?注意题目原文有一句:对于1到N这N个数字,都有唯一且不同的1到N的数字与之对应,也就是说,如果构建一张图,以数字为点,转化关系为边,共有n个点n条边,且每个点入度和出度均为1,即每个点只与另外两个点相连,又得到小结论:这是一张由环(或单独的点)构成的图,那么一个数必定能转化回原数

3、不难看出,由两个含有m1,m2个数字的可转化序列(2中的环)所组成的序列,回到原状态需要LCM(m1,m2)(最小公倍数)行,而数字在序列中的位置关系不影响答案,如{(1->3,3->5,5->1),(2->4,4->2)}和{(1->2,2->3,3->1),(4->5,5->4)}是等价的

4、现在问题可抽象为:共有n个元素,将它分为m个集合,求每个集合元素个数的LCM可能出现的情况

5、对于每个LCM,可将它分解为质因数的乘积,L=(p1 ^ k1) (p2 ^ k2) ······* (pi ^ ki),而它的每个质因子都来自 “集合中的元素个数”,要用有限的元素个数得到尽可能多的LCM,就要使“集合中的元素个数”互质,这样LCM即为它们的乘积,不需要除以GCD导致损失,因此这个L能被凑出,当且仅当p1 ^ k1+p2 ^ k2+······+pi^ki<=n,注意可以将一个数单独分组,所以和可以小于n

6、用素数筛预处理出不大于n的素数

7、用f[i][j]表示前i个质数总和为j的情况数,得到转移方程f [i] [j] = Σ f [i-1] [j-pi^k] (k>=0 && j-pi^k>=0)

8、因为没有用第i个质数也是一种情况,所以f [i] [j] 一定包含f [k] [j] (0<=k<=i)的所有情况,可以把数组压缩到一维,即f [j] 表示总和为j的情况数

9、由于每一个i对应的f [j] 都由i-1对应的f [k] (k<=j)推得,循环j时需要从后向前

10、初始化f [0]=1

.
.
.
.
.
.
程序:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

long long n,k=1,f[1010],p[500];

int main()
{
	scanf("%d",&n);
	if (n==1)
	{
		printf("1");
		return 0;
	}
	p[k]=2; 
	for (int i=3;i<=n;i+=2)
	{
        p[++k]=i;
        for (int j=2;j<k;j++)
            if (i%p[j]==0)
			{
                k--;
                break;
            }
    }
    f[0]=1;
    for (int i=1;i<=k;i++)
        for (int j=n;j>=p[i];j--)
		{
            int x=p[i];
            while (x<=j)
			{
                f[j]+=f[j-x];
                x*=p[i];
            }
        }
    long long sum=0;
    for (int i=1;i<=n;i++)
		sum+=f[i];
    printf("%lld",sum+1);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值