暑假習題 五

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)時爲奇數,否則爲偶數。

由於方法二的神奇 這裏就省略代碼了…..

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章