实用算法实践-第 30 篇 组合数学

本文探讨了Catalan数在PKU JudgeOnline问题1095中的应用,以及Fibonacci数列的性质和递归求解方法,特别是关于Fibonacci数列模k的循环节长度问题。通过实例和分析,展示了如何使用Pólya计数定理解决不同着色方案的计算问题,并提供了相关问题的程序实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

30.1    Ctalan数

PKU JudgeOnline, 1095, Trees Made to Order.

30.2    Fibonacci数

Fibonacci数列的定义如下:

f(n) = f(n - 1) + f(n - 2) (n >= 3)

f(1) = 1, f(2) = 2

f(0)可定义为1。

用归纳法可以证明性质:

f(n + m) = f(m - 1)f(n + 1) + f(m - 2)f(n)  (m>= 2)

利用这条性质,我们可以将比较大的n的Fibonacci数转化成比较小的Fibonacci数,从而使计算起来更为方便。

这里有一个问题:

Fibonacii数列 Fn (mod k) 的循环节长度是多少?有没有关于k的通项公式或者计算方法?

30.2.1   实例

PKU JudgeOnline, 3070, Fibonacci.

30.2.2   问题描述

给定n,要求第n个Fibonacci数mod 10000的结果。

30.2.3   输入

0

9

999999999

1000000000

-1

30.2.4   输出

0

34

626

6875

30.2.5   分析

可以通过Fibonacci的性质和模运算的性质对之进行递归求解。通过鸽巢原理可以知道输出必定是一个循环。如果知道循环节,该问题就更简单了。循环节也很好找。

30.2.6   程序

#include <iostream>
#include <math.h>
#include <string.h>
using namespace std;
#define repeatTime	50
#define __int32Max 2147483648
//0x7FFFFFFF = 2147483647 = 536870911.75 * 4
#define maxSize 5000000
int F[maxSize];
int ModularFibonacci(int n, int p)
{
	//0, 1, 1, 2, 3, 5, 8, 13, 21, 34, …
	//0  1  2  3  4  5  6  7   8   9
	int temp;
	if(n < 0)
		return 0;
	if(n < maxSize && F[n] != 0){
		return F[n];
	}
	if(n % 2 == 0){
		temp = ((ModularFibonacci(n / 2 - 1, p) * ModularFibonacci(n / 2 + 1, p)) + (ModularFibonacci(n / 2 - 2, p) * ModularFibonacci(n / 2, p))) % p;
	}else{
		temp = ((ModularFibonacci(n / 2, p) * ModularFibonacci(n / 2 + 1, p)) + (ModularFibonacci(n / 2 - 1, p) * ModularFibonacci(n / 2, p))) % p;
	}
	if(n < maxSize){
		F[n] = temp;
	}
	return temp;
}
int main()
{
	int n;
	memset(F, 0, sizeof(F));
	F[0] = 1;
	F[1] = 1;
	F[2] = 2;
	F[3] = 3;
	F[4] = 5;
	while(cin >> n && n != -1){
		cout<< ModularFibonacci(n - 1, 10000) << endl;
	}
	return 1;
}

30.3    Pólya计数定理

Pólya计数定理是组合数学种一个非常重要的定理。它可以用来解决类似于计算不同着色方案数的问题。

《算法艺术与信息学竞赛》从着色方案的角度介绍了Pólya计数定理。相对于组合数学教材中的Pólya计数定理的形式化描述,该介绍较为容易理解。不过似乎该文介绍的只是Pólya计数定理的一种特殊化表达。

30.3.1   实例

PKU JudgeOnline, 2409, Let it Bead.

30.3.2   问题描述

一个手镯由c种颜色的s个珠子串起来。问对已知的c和s,手镯可以有多少种。

30.3.3   输入

11

21

22

51

25

26

62

00

30.3.4   输出

1

2

3

5

8

13

21

30.3.5   分析

这个问题是基本的着色问题。解决这类问题,可按照如下的步骤进行:

1. 分析置换群的种类和个数。

2. 分析每个置换的循环分解式和循环节。

3. 根据Pólya计数定理计算方案数。

对于这个问题,可以知道置换既可以是为旋转(rotation),也可以是翻转(reflection)。旋转和翻转的类型分别为s种。旋转每增加一个360/s度,对应于升序置换的一次循环右移。翻转轴旋转一个360/s度,对应于降序置换的一次循环右移。所以置换很容易计算出来。

有了置换的具体表示,其循环分解式和循环节就很容易计算出来了。

       对每种置换进行计算,根据公式求和,就算出了着色的不同方案。

30.3.6   程序

#include<string.h>
#include<stdio.h>
#include<iostream>
using namespace std;
int permutation[100];
int c;
int s;
void visit1(int i)
{//输出循环分解式
     int j;
     int temp;
     j = i;
     while(permutation[j]!= i){
         cout << j <<" ";
         temp = permutation[j];
         permutation[j] = 0;
         j = temp;
     }
     cout << j <<" ";
     permutation[j] = 0;
     cout << endl;;
}
void visit(int i)
{//不输出循环分解式,仅用来计算循环节
     int j;
     int temp;
     j = i;
     while(permutation[j]!= i){
         temp = permutation[j];
         permutation[j] = 0;
         j = temp;
     }
     permutation[j] = 0;
}
int cycleNum()
{//计算置换的循环节
     int num;
     int i;
     num = 0;
     for(i = 1;i <= s; i++){
         if(permutation[i]!= 0)
         {
              visit(i);
              num++;
         }
     }
     return num;
}
int exponent(int a, intb)
{//计算a^b b必须大于
     int i;
     int temp;
     temp = 1;
     for(i = 0;i < b; i++){
         temp = temp * a;
     }
     returntemp;
}
int main()
{
     int i, j;
     intgroupSize;
     int sum;
     int temp;
     while(cin>> c >> s){
         if(c ==0 && s == 0)
         {
              break;
         }
         //cout<< cycleNum();
         sum = 0;
         for(i =0; i < s; i++){
              for(j= 1; j <= s - i; j++){
                   permutation[j] = j + i;
              }
              for(;j <= s; j++){
                   permutation[j] = j + i - s;
              }
              temp = cycleNum();
              sum += exponent(c, temp);
         }
         for(i =1; i <= s; i++){
              for(j= 1; j <= i; j++){
                   permutation[j] = i - j + 1;
              }
              for(;j <= s; j++){
                   permutation[j] = i + s - j +1;
              }
              temp = cycleNum();
              sum += exponent(c, temp);
         }
         groupSize = s << 1;
         sum = sum / groupSize;
         cout << sum << endl;
     }
}

 本文章欢迎转载,请保留原始博客链接http://blog.youkuaiyun.com/fsdev/article

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值