暑假习题 五

来源:互联网 发布:阜宁县网络发言人平台 编辑:程序博客网 时间:2024/05/17 11:05

Hanoi双塔问题
【题目描述】
给定A、B、C三根足够长的细柱,在A柱上放有2n个中间有孔的圆盘,共有n个不同的尺寸,每个尺寸都有两个相同的圆盘,注意这两个圆盘是不加区分的(下图为n=3的情形)。现要将这些圆盘移到C柱上,在移动过程中可放在B柱上暂存。要求:
(1)每次只能移动一个圆盘;
(2)A、B、C三根细柱上的圆盘都要保持上小下大的顺序;
任务:设An为2n个圆盘完成上述任务所需的最少移动次数,对于输入的n,输出An。
【输入描述】
输入文件:hanoi.in
为一个正整数n,表示在A柱上放有2n个圆盘。
【输出描述】
输出文件:hanoi.out
仅一行,包含一个正整数, 为完成上述任务所需的最少移动次数An。
【样例输入】
2
【样例输出 】
6
【数据范围】
对于50%的数据,1<=n<=25
对于100%的数据,1<=n<=200

分析:
个人理解就是汉诺塔每个大小的多了一个,于是对于每个大小的我们移动的时候就多了一次。
我们很容易知道常规的汉诺塔的移动次数公式为 2^n-1;
于是对于双塔 我们就乘以 2
即 2*(2^n-1)
数据有点大 高精度啊

#include <cstdio>#include <cstring >#include <iostream >using namespace std;const int maxn = 20000;struct bign{int len, s[maxn];bign() {    memset(s, 0, sizeof(s));    len = 1;}bign(int num) {    *this = num;}bign(const char* num) {    *this = num;}bign operator = (int num) {    char s[maxn];    sprintf(s, "%d", num);    *this = s;    return *this;}bign operator = (const char* num) {    len = strlen(num);    for(int i = 0; i < len; i++) s[i] = num[len-i-1] - '0';    return *this;}string str() const {    string res = "";    for(int i = 0; i < len; i++) res = (char)(s[i] + '0') + res;    if(res == "") res = "0";    return res;}bign operator + (const bign& b) const{    bign c;    c.len = 0;    for(int i = 0, g = 0; g || i < max(len, b.len); i++) {      int x = g;      if(i < len) x += s[i];      if(i < b.len) x += b.s[i];      c.s[c.len++] = x % 10;      g = x / 10;    }    return c;}void clean() {    while(len > 1 && !s[len-1]) len--;}bign operator * (const bign& b) {    bign c; c.len = len + b.len;    for(int i = 0; i < len; i++)      for(int j = 0; j < b.len; j++)        c.s[i+j] += s[i] * b.s[j];    for(int i = 0; i < c.len-1; i++){      c.s[i+1] += c.s[i] / 10;      c.s[i] %= 10;    }    c.clean();    return c;}bign operator - (const bign& b) {    bign c; c.len = 0;    for(int i = 0, g = 0; i < len; i++) {      int x = s[i] - g;      if(i < b.len) x -= b.s[i];      if(x >= 0) g = 0;      else {        g = 1;        x += 10;      }      c.s[c.len++] = x;    }    c.clean();    return c;}bool operator < (const bign& b) const{    if(len != b.len) return len < b.len;    for(int i = len-1; i >= 0; i--)      if(s[i] != b.s[i]) return s[i] < b.s[i];    return false;}bool operator > (const bign& b) const{    return b < *this;}bool operator <= (const bign& b) {    return !(b > *this);}bool operator == (const bign& b) {    return !(b < *this) && !(*this < b);}bign operator += (const bign& b) {    *this = *this + b;    return *this;}};istream& operator >> (istream &in, bign& x) {string s;in >> s;x = s.c_str();return in;}ostream& operator << (ostream &out, const bign& x) {out << x.str();return out;}int main(){  int n;bign a=2;  scanf("%d",&n);  for(int i=2;i<=n;i++){   a=a*2;  }  bign c=a-1;  cout<<c*2;  return 0;}

矩阵取数游戏
【问题描述】
帅帅经常跟同学玩一个矩阵取数游戏:对于一个给定的 的矩阵,矩阵中的每个元素 均为非负整数。游戏规则如下:
1.每次取数时须从每行各取走一个元素,共 个。 次后取完矩阵所有元素;
2.每次取走的各个元素只能是该元素所在行的行首或行尾;
3.每次取数都有一个得分值,为每行取数的得分之和,每行取数的得分=被取走的元素值 ,其中 表示第 次取数(从1开始编号);
4.游戏结束总得分为 次取数得分之和。
帅帅想请你帮忙写一个程序,对于任意矩阵,可以求出取数后的最大得分。
【输入】
输入文件game.in包括 行:
第1行为两个用空格隔开的整数 和 。
第2~ 行为 矩阵,其中每行有 个用单个空格隔开的非负整数。
【输出】
输出文件game.out仅包含1行,为一个整数,即输入矩阵取数后的最大得分。
【输入输出样例】
game.in
2 3
1 2 3
game.out
3 4 2 82

【输入输出样例1解释】
第1次:第1行取行首元素,第2行取行尾元素,本次得分为1*21+2*21=6
第2次:两行均取行首元素,本次得分为2*22+3*22=20
第3次:得分为3*23+4*23=56。总得分为6+20+56=82

分析:
一道动规
每一排是相对独立的。
每一排的处理实际上是一个动态规划的过程,下面具体说明动态规划的状态设计和转移策略。
1)设f[i,j]表示在当前这一排,前面取到了i,后面取到了j,即还剩余区间为[i,j]的最优值。
2)最优性:不管i和j如何分布,每一轮过后剩下的数的数目是一定的,而且在[i+1,j-1]这个区间内,其它的数都不能取。那么我们只要保证其它的数达到最优,在加上未取的部分后才可能达到最优;否则我们总可以用取得当前最优值的方案来取代原有的方案而取得更有值,这与原方案为最优值是矛盾的。因此,这样的状态满足最优性。
3)无后效性:因为只能取每一行的首尾元素中的某一个,所以转移到f[i,j]只有两种可能:
f[i-1,j]或f[i,j+1],前者表示这一轮取走了元素i,后者表示这一轮取走了元素j,显然取过i和j后不会再取得i和j,那么满足无后效性。
4)转移:由无后效性中的证明可以看出:
f[i,j]=max{f[i-1,j]+data[i-1]*2^k,f[i,j+1]+data[j+1]*2^k}
则每一行的最大得分为max(f[i][i-1])
3 因为m<=80,所以最多可能有lg(1000*281)≈29位,所以需要用高精度计算。

//这里就不把高精度写出来了,记住补全啊int main(){       int n,m,a[100];    scanf("%d%d",&n,&m);    pow[0]=1;    //get pow ;     for(int i=1;i<=m;i++)    pow[i]=pow[i-1]*2;    //对每一排考虑 实际上 每一排跟下一排没得关系找到当前最大     for(int r=1;r<=n;r++)        {            for(int c=1;c<=m;c++)                scanf("%d",&a[c]);            for(int i=1;i<=m;i++)                f[i][i]=pow[m]*a[i];            //DP            for(int i=m-1;i>=1;i--)                for(int j=i+1;j<=m;j++)                    {if(f[i+1][j]+pow[m-j+i]*a[i] > f[i][j-1]+pow[m-j+i]*a[j])                      f[i][j]=f[i+1][j]+pow[m-j+i]*a[i];                      else f[i][j] = f[i][j-1]+pow[m-j+i]*a[j];                    }            ans=ans+f[1][m];        }    cout<<ans;    return 0;}

组合数
【题目描述】
最近老师教了小明怎么算组合数,小明又想到了一个问题。。。
小明定义C(N,K)表示从N个元素中不重复地选取K个元素的方案数。
小明想知道的是C(N,K)的奇偶性。
当然,这个整天都老是用竖式算123456789*987654321=?的人不会让你那么让自己那么轻松,它说:“N和K都可能相当大。”
但是小明也犯难了,所以它就找到了你,想请你帮他解决这个问题。

【输入描述】
输入文件:comb.in
第1行:一个正整数t,表示数据的组数。
第2~2+t-1行:两个非负整数N和K。(保证k<=n)

【输出描述】
输出文件:comb.out
对于每一组输入,如果C(N,K)是奇数则输出1,否则输出0。
【样例输入】
3
1 1
1 0
2 1
【样例输出】
1
1
0
【数据范围】
对于30% 的数据,n<=10^2 t<=10^4
对于50% 的数据,n<=10^3 t<=10^5
对于100%的数据,n<=10^8 t<=10^5

分析:
数论题都好高深
方法一:统计n!质因子中2的个数
n! = 2^a * M
a=[n/2]+[n/(2^2)]+[n/(2^3)]+ … +[n/(2^g)] (2^g<=n)
证明:加法原理
所以C(N,K)质因子中2的个数为 F = f(N) - f(K) - f(N - K)
若F大于0,则为偶数,等于零为奇数。

*方法二:由Lucas定理得当k==(k&n)时为奇数,否则为偶数。

由于方法二的神奇 这里就省略代码了…..

0 0
原创粉丝点击