BZOJ1025 [SCOI2009]游戏

题意:对于一些长度为n的排列,将其作为一个置换,那么可能有一个自置换的次数使其回到1,2,3,...,n的情况。求对于所有能够回到1,2,3..,n的排列,不同的次数共有多少种。


思路:我们将置换划分成循环节的形式,那么我们发现最终可能的置换一定是这种形式:

(2,1)(3)(5,6,4)(7)

1,2->2,1

3->3

4,5,6->5,6,4

7->7

并且,若一段的长度为L,那么在第一行下面就是每L行一个循环。最后一行就是我们想要的正序排列。

那么总的行数就是1+LCM(L1,L2,L3,L4,...Lk).

其中k表示划分为k段,并且有L1+L2+L3+...+Lk=n.

于是问题转化为k个正整数,和为n,求他们的最小公倍数的数目。

事实上,k是不确定的,并不容易处理。于是我们转而考虑某个答案ans=p1^a1*p2^a2*..*pm^am是否是n的一个可行答案。


我们有以下结论:若p1^a1+p2^a2+...+pm^am<=n,则ans=p1^a1*p2^a2*..*pm^am是n的一个可行答案。

这个现在我只能给出一种比较正确的证明。

利用反证法证明:不妨设n的某个可行答案ans=p1^a1*p2^a2*p3^a3*...*pm^am,且p1^a1+p2^a2+p3^a3+...+pm^am>n.

由于p1^a1在LCM中,那么必然有某个L是p1^a1的倍数。但是有一些L非常牛,可能是多个pi^ai的倍数。


不妨举个小例子:

设L1是p1^a1的倍数,L3是p2^a2*p3^a3*p4^a4的倍数(L2呢?打酱油去啦~~),L4是p5^a5的倍数,L5是p6^a6*p7^a7的倍数,剩下的L都打酱油去了。

然后显然有L1+L3+L4+L5=b1*p1^a1+b2*p2^a2*p3^a3*p4^a4+b3*p5^a5+b4*p6^a6*p7^a7<=n(因为有一些L打酱油去了)

这里有b1,b2,b3,b4为正整数。

而又有p1^a1+p2^a2+p3^a3+...+p7^a7>n,那么由于都是大于1的数,我们可以认为在每一段内都有乘积比和要大,再乘以一个倍数,必然也比和要大,那么就有:

L1+L3+L4+L5=b1*p1^a1+b2*p2^a2*p3^a3*p4^a4+b3*p5^a5+b4*p6^a6*p7^a7>n

产生矛盾!

那么我们就证明了上述结论。


那么现在问题就转化为求有多少种(a1,a2,a3,...am),满足p1^a1+p2^a2+p3^a3+...+pm^am<=n

利用分组背包就可以通过了。

注意边界条件。


代码:

/**
 * Author: wyfcyx
 * Problem: BZOJ1025
 * Keywords: Math, Dynamic Programming
 * Time Complexity: O(N*N/logN*logN)=O(N^2)
 * Created Time: 14-10-18
 **/

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

const int N = 1000;
int p[N], id;
bool notp[N + 1];
void pre() {
	int i, j;
	for(i = 2; i <= N; ++i) {
		if (!notp[i])
			p[++id] = i;
		for(j = 1; j <= id && i * p[j] <= N; ++j) {
			notp[i * p[j]] = 1;
			if (i % p[j] == 0)
				break;
		}
	}
}

unsigned long long f[200][1001];

int main() {
	int n;
	scanf("%d", &n);
	
	pre();
	
	register int i, j, k;
	for(i = 0; i <= id; ++i)
		f[i][0] = 1;
	for(j = 1; j <= n; ++j)
		f[0][j] = 1;
	for(i = 1; i <= id; ++i) {
		for(j = 1; j <= n; ++j) {
			f[i][j] = f[i - 1][j];
			for(k = p[i]; k <= j; k *= p[i])
				f[i][j] += f[i - 1][j - k];
		}
	}
	printf("%llu", f[id][n]);
	
	return 0;
}


  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值