ACM Steps_Chapter Two_Section2

Fibonacci

/*
用到了斐波那契数列的通项公式。
先看对数的性质,loga(b^c)=c*loga(b),loga(b*c)=loga(b)+loga(c);
假设给出一个数10234432,那么log10(10234432)=log10(1.0234432*10^7)=log10(1.0234432)+7;

log10(1.0234432)就是log10(10234432)的小数部分.

log10(1.0234432)=0.010063744
10^0.010063744=1.023443198
那么要取几位就很明显了吧~
先取对数(对10取),然后得到结果的小数部分bit,pow(10.0,bit)以后如果答案还是<1000那么就一直乘10。
注意偶先处理了0~20项是为了方便处理~

这题要利用到数列的公式:an=(1/√5) * [((1+√5)/2)^n-((1-√5)/2)^n](n=1,2,3.....)


取完对数


log10(an)=-0.5*log10(5.0)+((double)n)*log(f)/log(10.0)+log10(1-((1-√5)/(1+√5))^n)其中f=(sqrt(5.0)+1.0)/2.0;
log10(1-((1-√5)/(1+√5))^n)->0
所以可以写成log10(an)=-0.5*log10(5.0)+((double)n)*log(f)/log(10.0);
最后取其小数部分。
*/

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
int f[21] = {0, 1, 1};

int main()
{
	int n;
	for(int i = 2; i < 21; ++i)
		f[i] = f[i - 1] + f[i - 2];
	while(scanf("%d", &n) != EOF)
	{
		if(n <= 20)
		{
			printf("%d\n", f[n]);
			continue;
		}
		else
		{
			double temp = -0.5 * log(5.0) / log(10.0) + ((double)n) * log((sqrt(5.0)+1.0)/2.0) / log(10.0);
			temp -= floor(temp);
			temp = pow(10.0, temp);
			while(temp < 1000)
				temp *= 10;
			printf("%d\n", (int)temp);
		}
	}
	return 0;
}

Joseph

#include<iostream>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
using namespace std;
int res[16];

int fun(int n)
{
	int ans, flag, sum;
	if(res[n])
		return res[n];
	else
	{
		for(ans = n + 1; ; ++ans)
		{
			flag = 0;
			sum = 2 * n;
			for(int j = ans; ; j += ans - 1) //人数减1
			{
				if(j > sum)
					j = j % sum ? j % sum : sum; //人数减1
				if(j <= n) 
					break;
				else	
					sum--;
				if(sum == n)
				{
					flag = 1;
					break;
				}
			}
			if(flag)
			{
				res[n] = ans;
				return res[n];
			}
		}
	}
}

int main()
{
	int n;
	while(scanf("%d", &n) && n)
	{
		printf("%d\n", fun(n));
	}
	return 0;
}        

汉诺塔VII

/*
对一个含有n个盘子,从A柱移动到C柱借助B柱的汉诺塔,
第n个盘子移动到C柱过程是这样子的:首先将其余的n-1个盘子移动到B柱,
然后第n个盘子直接移动到C柱。在这过程中,第n个盘子只出现在A柱和C柱两个柱子上,
也即第n个盘子不可能出现在B柱上。因此对于当前移动的盘子,
只需检查其所在的柱子是否符合这个要求,如果出现在B柱上,
则显然进入了错误移动中。这是本题求解思想精髓所在。汉
诺塔是个递归求解过程,假设第n个盘子符合要求,则判别的下一个目标是第n-1个盘子。
若第n个盘子在A柱上,此时剩余n-1个盘子必由A柱移动到B柱,经由C柱。
此时对第n-1个盘子,C柱就是其不可能出现的位置;若第n个盘子在C住上,
这剩余n-1个盘子则是在B柱上,经由A柱,移动到C柱上,
因此,A柱就是第n-1个盘子不可能出现的位置。
*/

#include<iostream>
using namespace std;

bool flag;
void DFS(int n,int *A,int *B,int *C){
    if(n==0){
        flag = true;
        return ;
    }
    if(B[0]&&n==B[1]){
        flag = false;
        return ;
    }
    if(A[0]&&n==A[1]){
        A[1]=A[0]-1;
        DFS(n-1,++A,C,B);
    }else if(C[0]&&n==C[1]){
        C[1]=C[0]-1;
        DFS(n-1,B,A,++C);
    }
}
int main(){
    int A[70],B[70],C[70];
    int T,n;
    cin>>T;
    while(T--){
        cin>>n;
        cin>>A[0];
        for(int i=1;i<=A[0];i++)
            cin>>A[i];
        cin>>B[0];
        for(int i=1;i<=B[0];i++)
            cin>>B[i];
        cin>>C[0];
        for(int i=1;i<=C[0];i++)
            cin>>C[i];
        DFS(n,A,B,C);

        if(flag)
        cout<<"true"<<endl;
        else
        cout<<"false"<<endl;
    }
}

Wolf and Rabbit

#include<cstdio>
#include<cstdlib>
#include<cstring>
bool gcd(int m,int n)
{
	while(n%m)
	{
		int temp=n%m;
		n=m;
		m=temp;
	}
	return m==1?false:true;
}
int main()
{
	int m,n;
	int p;
	scanf("%d",&p);
	for(int i=0;i<p;i++)
	{
		scanf("%d%d",&m,&n);
		printf("%s\n",gcd(m,n)?"YES":"NO");
	}
	return 0;
}

献给杭电五十周年校庆的礼物

/*
①n条直线把平面分割成的区域数: f(n)=f(n-1)+n=n(n+1)/2+1;

②把空间分割为最多的区域数的时候,第n个平面与前(n-1)个平面相交,
且无三面共线,所以此时该平面与前(n-1)个平面有(n-1)条交线。
这些交线把第n个平面分割为f(n-1)个区域,于是这个平面将原有空间一分为二,
故增加了f(n-1)个空间,得递推公式:g(n)=g(n-1)+f(n-1)=(n^3+5n)/6+1。
*/
#include<iostream>
using namespace std;
int main()
{
 	int n;
 	while(cin>>n)
  		cout<<(n*n*n+5*n+6)/6<<endl;
 	return 0;
}

Train Problem II

/*
出栈次序
一个栈(无穷大)的进栈序列为1,2,3,…,n,有多少个不同的出栈序列?[4-5]
  
常规分析
首先,我们设f(n)=序列个数为n的出栈序列种数。同时,我们假定,
从开始到栈第一次出到空为止,这段过程中第一个出栈的序数是k。
特别地,如果栈直到整个过程结束时才空,则k=n
首次出空之前第一个出栈的序数k将1~n的序列分成两个序列,其中一个是1~k-1,
序列个数为k-1,另外一个是k+1~n,序列个数是n-k。
此时,我们若把k视为确定一个序数,那么根据乘法原理,
f(n)的问题就等价于——序列个数为k-1的出栈序列种数乘以序列个数为n - k的出栈序列种数,
即选择k这个序数的f(n)=f(k-1)×f(n-k)。而k可以选1到n,
所以再根据加法原理,将k取不同值的序列种数相加,得到的总序列种数为:
f(n)=f(0)f(n-1)+f(1)f(n-2)+……+f(n-1)f(0)。
看到此处,再看看卡特兰数的递推式,答案不言而喻,
即为f(n)=h(n)= C(2n,n)/(n+1)= c(2n,n)-c(2n,n+1)(n=0,1,2,……)。
最后,令f(0)=1,f(1)=1。
非常规分析
对于每一个数来说,必须进栈一次、出栈一次。我们把进栈设为状态‘1’,出栈设为状态‘0’。
n个数的所有状态对应n个1和n个0组成的2n位二进制数。
由于等待入栈的操作数按照1‥n的顺序排列、入栈的操作数b大于等于出栈的操作数a(a≤b),
因此输出序列的总数目=由左而右扫描由n个1和n个0组成的2n位二进制数,
1的累计数不小于0的累计数的方案种数。
在2n位二进制数中填入n个1的方案数为c(2n,n),不填1的其余n位自动填0。
从中减去不符合要求(由左而右扫描,0的累计数大于1的累计数)的方案数即为所求。
不符合要求的数的特征是由左而右扫描时,
必然在某一奇数位2m+1位上首先出现m+1个0的累计数和m个1的累计数,
此后的2(n-m)-1位上有n-m个 1和n-m-1个0。如若把后面这2(n-m)-1位上的0和1互换,
使之成为n-m个0和n-m-1个1,结果得1个由n+1个0和n-1个1组成的2n位数,即一个不合要求的数对应于一个由n+1个0和n-1个1组成的排列。
反过来,任何一个由n+1个0和n-1个1组成的2n位二进制数,由于0的个数多2个,
2n为偶数,故必在某一个奇数位上出现0的累计数超过1的累计数。
同样在后面部分0和1互换,使之成为由n个0和n个1组成的2n位数,
即n+1个0和n-1个1组成的2n位数必对应一个不符合要求的数。
因而不合要求的2n位数与n+1个0,n-1个1组成的排列一一对应。
显然,不符合要求的方案数为c(2n,n+1)。
由此得出输出序列的总数目=c(2n,n)-c(2n,n+1)=c(2n,n)/(n+1)=h(n+1)。
*/


#include<iostream>
using namespace std;
int a[101][101]={0};
int main()
{
    int n,i,j,len,r,temp,t;
    int b[101];
    a[1][0] = 1;
    len = 1;
    b[1] = 1;
    for(i=2;i<=100;i++)
    {
        t = i-1;
        for(j=0;j<len;j++) //乘法
            a[i][j] = a[i-1][j]*(4*t+2);
        for(r=j=0;j<len;j++)  //处理相乘结果
        {
            temp = a[i][j] + r;
            a[i][j] = temp % 10;
            r = temp / 10;
        }
        while(r) //进位处理
        {
            a[i][len++] = r % 10;
            r /= 10;
        }

        for(j=len-1,r=0;j>=0;j--) //除法
        {
            temp = r*10 + a[i][j];
            a[i][j] = temp/(t+2);
            r = temp%(t+2);
        }
        while(!a[i][len-1]) //高位零处理
            len --;
        b[i] = len;
    }
    while(cin>>n)
    {   
        for(j=b[n]-1;j>=0;j--)
            printf("%d",a[n][j]);
        printf("\n");
    }
    return 0;
}

Examining the Rooms

/*
首先这题其实让我们求的是给 N个元素,让我们求K个环排列的 方法数。

斯特林第一类数的第推公式:

S(N,0)=0;

S(N,N)=1;

S(0,0)=0;

S(N,K)=S(N-1,K-1)+S(N-1,K)*(N-1);

这个公式的意思是:

当前N-1个数构成K-1 个环的时候,加入第N个 ,N只能构成单环!—S(N-1,K-1)

如果N-1个数构成K个环的时候,加入第N个,N可以任意加入,N-1内的一个环里,
所以是–(N-1)*S(N-1,K)

这个题目里,因为不能破坏第1个门,
所以S(N,K)-S(N-1,K-1)才是能算构成K个环的方法数!就是去掉1自己成环的情况!

*/

#include<iostream>
#include<cstdio>
using namespace std;
const int N = 21;
long long ans[N][N],f[N];
int main()
{
	int t,n,k;
	ans[1][1]=1;
	f[0]=f[1]=1;
	for(int i=2;i<N;i++)
	{
		for(int j=1;j<=i;j++)
		{
			ans[i][j]=ans[i-1][j-1]+(i-1)*ans[i-1][j];
		}
		f[i]=f[i-1]*i;
	}
	cin>>t;
	while(t--)
	{
		cin>>n>>k;
		long long sum=0;
		for(int i=1;i<=k;i++)
		{
			sum+=ans[n][i]-ans[n-1][i-1];
		}
		printf("%.4lf\n",(double)sum/f[n]);
	}
	system("pause");
	return 0;
}
	

Big Number

/*
我们有公式, log10(n!)=(0.5*log(2*PI*n)+n*log(n)-n)/log(10) , 
这里利用了 log10 (N)=ln(N)/ln(10);

公式的名字是 斯特林公式
*/


#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<string.h>
#include<time.h>
#define PI 3.1415926
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int digits,n;
        scanf("%d",&n);
        if(n==0)  //没有也过了..
        {
            printf("1\n");
            continue;
        }
        digits=(int)((0.5*log(2*PI*n)+n*log(n)-n)/log(10));
        printf("%d\n",digits+1);   //加1是必须的。
    }
    return 0;
}


posted @ 2013-04-21 20:26  VeryBigMan  阅读(130)  评论(0编辑  收藏  举报