申請SAE

如果您發現本博客的外觀很難看,那是因為部分外觀文件被中國.國家.防火.牆屏.蔽所致!
請翻~牆!

我的Wordpress博客的地址: http://zhuyf.tk/

2011年10月24日 星期一

USACO搜索練習題 Cow Yahtzee [cowyotz]

Cow Yahtzee  [USACO 2006-2007 Feb07]
In their usual clumsy way, the cows are play a version of Yahtzee,
the dice-rolling game.  They roll N (1 <= N <= 20) dice, each having
S (1 <= S <= 8) sides.  They are curious as to the number of ways
a dice roll can meet a particular criterion (like "contains three
2's" or "contains one 2 and two 3's").

Help them learn about probability. Write a program that reads not
only N and S but also some expressions that describe their criteria.
Count the number ways the expression can be satisfied over the
entire set of all possible dice rolls (the entire set of rolls for
three two-sided dice is: {1,1,1; 1,1,2; 1,2,1; 1,2,2; 2,1,1; 2,1,2;
2,2,1; 2,2,2};

Expressions comprise combinations of a basic form that expresses
the thought "want at least W copies of result R". It looks like this:

WxR

where (0 <= W <= N and 1 <= R <= S). Each test run will supply E
expressions (1 <= E <= 20), each of which contains a number 1..10
of the basic forms separated by a '+', which means 'and' (see below).
The set of lines expresses the thought that is the 'inclusive or'
of each of the lines individually. Thus the pair of expressions
shown below means "at least three rolls of five OR both at least
one roll of 3 and also at least two rolls of 4":

3x5
1x3+2x4

Here are some of the combinations of four five-sided dice that
satisfy the above expression: 5,5,5,1; 4,5,5,5; 3,4,4,2; 3,4,4,3;
3,4,4,5; 4,4,5,3.

Programming note: Be sure to verify that you can read in two integers
from one line and a string from the next line. In some languages'
I/O schema, this is harder than it looks!

Also note that the total number of dice combinations will never
exceed 1,512,768 in the supplied test data.

PROBLEM NAME: cowyotz

INPUT FORMAT:

* Line 1: Three space-separated integers: N, S, and E

* Lines 2..E+1: Line i+1 describes expression i as above.

SAMPLE INPUT (file cowyotz.in):

4 5 2
3x5
1x3+2x4

INPUT DETAILS:

This is the encoding of the expression used as an example in the task text.

OUTPUT FORMAT:

* Line 1: A single integer that is the number of ways the
        expression(s) can be satisfied by rolling the dice in all
        combinations.

SAMPLE OUTPUT (file cowyotz.out):

63

OUTPUT DETAILS:

63 rolls satisfy the expression.
 
【分析】 
這是一道不算太難也不算太簡單的搜索題目,本題的難點有3個:
1.讀入骰子規則時對字符串的處理;
2.運用搜索解決排列組合問題;
3.判斷每種 骰子的結果是否符合規則。

我做這個題的時候使用了廣度優先搜索(=寬度優先搜索),結果是AEAAAAAAWA,80分;
我的一位同學(paulinsider@gmail.com)做的時候使用了深度優先搜索,即遞歸求解排列
組合。沒想到,他竟然全過,拿了100分!了不起啊!
 
 
【代碼】
<1>我的代碼  
AEAAAAAAWA ,80分  (A=結果正確,E=運行時出錯,W=結果錯誤)
 #include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
using namespace std;
int N,S,E;
int r=0;
int Total=0;
class Rules
{
public:
 int num;
 int Num[100];
 int Up[100];
 Rules()
 {
  num=0;
  for (int i=0;i<=99;i++)
  {
   Num[i]=0;
   Up[i]=0;
  }
 }
}R[1000];

void init()
{
 cin>>N>>S>>E;
 char str[1000];
 for (int i=1;i<=E;i++)
 {
  r++;
  for (int j=0;j<=999;j++) str[j]='\0';
  cin>>str;
  int len=strlen(str);
  int p=0;
  int n1=0;
  int n2=0;
  int nt=0;
  bool flag=true;
  while (p<len)
  {
   if(str[p]>='0' && str[p]<='9')
   {
    nt=nt*10+str[p]-'0';
    p++;
    continue;
   } 
   else
   {
    if (flag)
    {
     n1=nt;
     nt=0;
    }
    if (!flag)
    {
     n2=nt;
     nt=0;
    }
    
    if (str[p]=='x')
    {
     R[r].Num[R[r].num]=n1;
     flag=!flag;
     p++;
     continue;
    }
    if (str[p]=='+')
    {
     R[r].Up[R[r].num]=n2;
     flag=!flag;
     p++;
     R[r].num++;
     continue;
    }
   } 
  }
  n2=nt;
  R[r].Up[R[r].num]=n2;
  R[r].num++;
 }
}
void debug()
{
 cout<<r<<endl;
 for (int i=1;i<=r;i++)
 {
  for (int j=0;j<R[i].num;j++)
   cout<<i<<" "<<j<<" "<<R[i].Num[j]<<" "<<R[i].Up[j]<<endl;
 }
}

class QUEUE
{
public:
 int Nu;
 int Touzi[9];
 QUEUE()
 {
  Nu=0;
  for (int i=0;i<=9;i++)
   Touzi[i]=0;
 }
};
QUEUE Q[2000000];

void check(int x)
{
 int Want[10]={0};
 for (int i=1;i<=N;i++)
 {
  Want[Q[x].Touzi[i]]++;
 }
 
 bool yes;
 for (int i=1;i<=r;i++)
 {
  for (int j=0;j<R[i].num;j++)
  {
   yes=true;
   if ( Want[R[i].Up[j]]< R[i].Num[j])
   {
    yes=false;
    break;
   }
  } 
  if(yes) 
  {
   Total++;
   return;
  }
 }
}

void BFS()
{
 int big=1;
 int small=0;
 Q[0].Nu=0;
 while(small<big)
 {
  int TN=Q[small].Nu;
  if (TN==N)
  {
   check(small);
   small++;
   continue;
  }
  int Need=TN+1;
  for (int i=1;i<=S;i++)
  {
   for (int k=0;k<=TN;k++) 
    Q[big].Touzi[k]=Q[small].Touzi[k];
   Q[big].Nu=Need;   
   Q[big].Touzi[Need]=i;
   big++;
  }
  small++;
 }
}

int main()
{
 freopen("cowyotz.in","r",stdin);
 freopen("cowyotz.out","w",stdout);
 init();
 BFS();
 cout<<Total<<endl;
 return 0;
}



<2>我同學的代碼
AAAAAAAAAA,100分 (A=結果正確)
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
int number,m,n,q[1512770][9],ji=0,ji1=0,c=0,f[20][9],used[9]={0},answer=0;
bool check(int x);
void di(int x);
int main()
{
 freopen ("cowyotz.in","r",stdin);
 freopen ("cowyotz.out","w",stdout);
 scanf("%d%d%d\n",&number,&m,&n);
 di(1);
 for (int i=0;i<n;i++)
 {
  char s[50];
  cin>>s;
  int lq;
  lq=strlen(s);
  int j=0;
  while (j<lq)
  {
   int a,b;
   a=s[j]-'0';
   b=s[j+2]-'0';
   f[ji1][b]=a;
   if (s[j+3]=='+')
   {
    j+=4;
   }
   else
   {
    break;
   }
  }
  ji1++;
 }
 for (int o=0;o<ji;o++)
 {
  int u[9]={0};
  for (int i=1;i<=number;i++)
  {
   u[q[o][i]]++;
  }
  for (int i=1;i<=m;i++)
  {
   q[o][i]=u[i];
  }
  if (check(o))
  {
   answer++;
  }
 }
 printf("%d",answer);
 return 0;
}
void di(int x)
{
 q[ji][c]=x;
 if (c==number)
 {
  ji++;
  for (int i=1;i<=number;i++)
  {
   q[ji][i]=q[ji-1][i];
  }
  
 }
 else
 {
  for (int i=1;i<=m;i++)
  {
   c++;
   di(i);
   c--;
  }
 }
}
bool check(int x)
{
 int p=0;
 for (int j=0;j<n;j++)
 {
  p=0;
  for (int i=1;i<=m;i++)
  {
   if (f[j][i]==0)
   {
    continue;
   }
   if (q[x][i]<f[j][i])
   {
    p++;
    break;
   }
  }
  if (p==0)
  {
   return true;
  }
 }
 return false;
}

NOI2000 單詞查找樹 解題報告

            【NOI2000】单词查找树 
Description
在进行文法分析的时候,通常需要检测一个单词是否在我们的单词列表里。为了提高查找和定位的速度,通常都要画出与单词列表所对应的单词查找树,其特点如下:
1)根节点不包含字母,除根节点外每一个节点都仅包含一个大写英文字母;
2)从根节点到某一节点,路径上经过的字母依次连起来所构成的字母序列,称为该节点对应的单词。单词列表中的每个词,都是该单词查找树某个节点所对应的单词;
3)在满足上述条件下,该单词查找树的节点数最少。

Input
每组输入一个单词列表,每一行仅包含一个单词和一个换行/回车符。每个单词仅由大写的英文字符组成,长度不超过63个字符。每组数据总长度不超过32K,至少有一行数据。
Output
输出仅包含一个整数和一个换行/回车符。该整数为单词列表对应的单词查找树的节点数。
Sample Input
A
AN
ASP
AS
ASC
ASCII
BAS
BASIC
Sample Output
13

【閒扯】
話說NOI2000的題目真是水,第一題是【瓷片項鍊】,就是一個一元二次函數的最值問題,第二題是這個題,真是簡單啊!

 【分析】
單詞查找樹就是字典樹(Trie Tree),可以參看我以前的文章。先用字典樹把所有的字符串均插入字典樹,然後再遍歷(Travel)整個樹,先序、中序、後序,怎麼遍歷都可以,可以寫遞歸函數來壓縮代碼複雜度。

【滿分代碼】
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
using namespace std;

const int sonnum=26;
const char base='A';
class Trie
{
public:
    bool isStr;
    int Page;
    class Trie *son[sonnum];
};

Trie *NewTrie()
{
    Trie *temp=new Trie;
    temp->isStr=false;
    for (int i=0;i<sonnum;i++)
        temp->son[i]=NULL;
    return temp;
}

void Insert(Trie *pnt,char *s,unsigned int len)
{
    Trie *temp=pnt;
    for (unsigned int i=0;i<len;i++)
    {
        if (temp->son[s[i]-base]==NULL)
            temp->son[s[i]-base]=NewTrie();
        temp=temp->son[s[i]-base];
    }
    temp->isStr=true;
}

Trie *pnt=NewTrie();
int T=0;

void Digui(Trie *temp)
{
    T++;
    for (int i=0;i<sonnum;i++)
    {   
        if(temp->son[i]!=NULL)
            Digui(temp->son[i]);
    }
}

void init()
{
    char str[65];
    while (cin>>str)
        Insert(pnt,str,strlen(str));
    Digui(pnt);
}

int main()
{
   
    freopen("trie.in","r",stdin);
    freopen("trie.out","w",stdout);
    init();
    cout<<T<<endl;
    fclose(stdin);
    fclose(stdout);
    return 0;
}

用VB寫修改Windows開機磁盤掃描等待時間的小工具

   前段時間,我的電腦的E盤爆了,不僅文件無法存取,而且每次開機都要進入藍色的自檢畫面,最重要的是,E盤點檢測的過程非常慢,某天我實驗了一下,從早上6:30到晚上21:30共15個小時,E盤的檢測進度才走了10%!
   所以,我每次都手動跳過硬盤自檢。但是,10秒太短了,有時候沒來得及按下鍵,於是就得強制關機然後重新開機,很麻煩。
   於是,我就自己動手用VB寫了個修改Windows開機磁盤掃描等待時間的小工具,很好用,代碼也放上來了,歡迎下載。

EXE執行檔與源代碼打包下載:
http://zyf.ucoz.net/Download/Disk-Scan-Time-Changer.zip
或者:https://yeefanzhustudio.googlecode.com/files/Disk-Scan-Time-Changer.zip

程式截圖:

效果截圖:
EXAMPLE: AUTOCHK initiation countdown time
(default and changed)
 AUTOCHK Initiation Countdown Time - Change-default.jpg AUTOCHK Initiation Countdown Time - Change-changed.jpg

NOIP2000提高組複賽 方格取數 解題報告

題四. 方格取數
問題描述
設有N*N的方格圖(N<=10,我們將其中的某些方格中填入正整數,而其他的方格中則放入數字0。如下圖所示(見樣例):
                                       向右
      A  1    2    3   4    5    6    7    8
 0
 0
0
0
0
0
0
0
0
0
13
0
0
6
0
0
0
0
0
0
7
0
0
0
0
0
0
14
0
0
0
0
0
21
0
0
0
4
0
0
0
0
15
0
0
0
0
0
0
14
0
0
0
0
0
0
0
0
0
0
0
0
0
0

某人從圖的左上角的A 點出發,可以向下行走,也可以向右走,直到到達右下角的B點。在走過的路上,他可以取走方格中的數(取走後的方格中將變爲數字0)。
此人從A點到B 點共走兩次,試找出2條這樣的路徑,使得取得的數之和爲最大。
          輸 入
輸入的第一行爲一個整數N(表示N*N的方格圖),接下來的每行有三個整數,前兩個表示位置,第三個數爲該位置上所放的數。一行單獨的0表示輸入結束。
         輸 出
只需輸出一個整數,表示2條路徑上取得的最大的和。

     樣 例 :
輸 入
8
2 3 13
2 6 6
3 5 7
4 4 14
5 2 21
5 6 4
6 3 15
7 2 14
0 0 0
輸 出
67

【分析】
簡單動態規劃,DP兩條路徑即可。

 狀態設定:F[i][j][k][m]表示走到(i,j),(k,m)取的數的最大值。
                  mat[i][j]表示方格(i,j)裡的數。
狀態轉移方程: F[i][j][k][m]=max{F[i-1][j][k-1][m],F[i-1][j][k][m-1],F[i][j-1][k-1][m],F[i][j-1][k][m-1]}+A  (其中A的取值:如果i!=k || j!=m,A=mat[i][j]+mat[k][m];如果i==k&&j==m,A=mat[k][m])
 目標狀態:F[N][N][N][N]
【我的代碼】

#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;
int mat[11][11];
int F[11][11][11][11];
int N;

int Max(int a,int b,int c,int d)
{
    if(b>a)
        a=b;
    if(c>a)
        a=c;
    if(d>a)
        a=d;
    return a;
}

void init()
{
    cin>>N;
    int a,b,c;
    cin>>a>>b>>c;
    while (a!=0 || b!=0 ||c!=0)
    {
        mat[a][b]=c;
        cin>>a>>b>>c;
    }

    return;   
}

void dp()
{
    for (int i=1;i<=N;i++)
    {
        for (int j=1;j<=N;j++)
        {
            for (int k=1;k<=N;k++)
            {
                for (int m=1;m<=N;m++)
                {
                    if(i==k && j==m)
                    {
                        F[i][j][k][m]=Max(F[i-1][j][k-1][m],F[i-1][j][k][m-1],
                    F[i][j-1][k-1][m],F[i][j-1][k][m-1])+mat[k][m];
                        continue;
                    }
                    F[i][j][k][m]=Max(F[i-1][j][k-1][m],F[i-1][j][k][m-1],
                    F[i][j-1][k-1][m],F[i][j-1][k][m-1])+mat[i][j]+mat[k][m];
                }
            }
        }
    }
    cout<<F[N][N][N][N]<<endl;
}

int main()
{
    freopen("fgqs.in","r",stdin);
    freopen("fgqs.out","w",stdout);
    init();
    dp();
    return 0;
}

2011年10月23日 星期日

NOIP2004提高組複賽 合併果子 fruit 解題報告

二、合併果子
(fruit.pas/dpr/c/cpp)
【問題描述】
在一個果園裏,多多已經將所有的果子打了下來,而且按果子的不同種類分成了不同的堆。多多決定把所有的果子合成一堆。
每一次合併,多多可以把兩堆果子合併到一起,消耗的體力等於兩堆果子的重量之和。可以看出,所有的果子經過n-1次合併之後,就只剩下一堆了。多多在合併果子時總共消耗的體力等於每次合併所耗體力之和。
因爲還要花大力氣把這些果子搬回家,所以多多在合併果子時要儘可能地節省體力。假定每個果子重量都爲1,並且已知果子的種類數和每種果子的數目,你的任務是設計出合併的次序方案,使多多耗費的體力最少,並輸出這個最小的體力耗費值。
例如有3種果子,數目依次爲1,2,9。可以先將1、2堆合併,新堆數目爲3,耗費體力爲3。接着,將新堆與原先的第三堆合併,又得到新的堆,數目爲12,耗費體力爲12。所以多多總共耗費體力=3+12=15。可以證明15爲最小的體力耗費值。
【輸入文件】
輸入文件fruit.in包括兩行,第一行是一個整數n(1<=n<=10000),表示果子的種類數。第二行包含n個整數,用空格分隔,第i個整數ai(1<=ai<=20000)是第i種果子的數目。
【輸出文件】
輸出文件fruit.out包括一行,這一行只包含一個整數,也就是最小的體力耗費值。輸入數據保證這個值小於231。
【樣例輸入】
3 1 2 9
【樣例輸出】
15
【數據規模】
對於30%的數據,保證有n<=1000: 對於50%的數據,保證有n<=5000; 對於全部的數據,保證有n<=10000。 
(感謝BYVoid大牛的OpenCC提供強力的正體中文轉換引擎!)
【分析】

注意這道題不是石子歸併類DP!本題的策略是貪心,每次找到最小的兩堆果子,將它們合併,代價就是這兩堆果子的數目和。
   本題可以用快速排序做,每次合併前先快排一次,不過這樣比較慢,最後幾組只能擦著1秒的邊過。
    我第一次寫該題用了靜態鏈表,是插入排序的一種,效率稍好,最後幾組測試數據平均0.6s多。
    我第二次寫該題時,用了本題的標準算法——堆排序。每次合併前維護一下這個小根堆,時間複雜度很低,我的程序沒有一組的運行時間超過0.1s。

根優化的小根堆算法:見本文

本題幾種算法的橫向對比!

 快速排序:3.749 s
 本文靜態鏈表:2.453 s 
 本文小根堆:0.109 s
 此文小根堆一:0.053 s
 此文小根堆二:0.037 s

【代碼】
<1>用鏈表寫的。
#include <fstream>
using namespace std;   
ifstream fin("fruit.in");
ofstream fout("fruit.out");
class data
{
public:
    int key;
    //Other members HERE...
    int sen;//前驱
    int next;//后继
};
data A[200000];
int top;//链表中元素的个数

void Insert(int key)
{
    int point=0;
    while (key>A[point].key)
    {
        point=A[point].next;
    }
    //Create a new node
    A[top].key=key;
    A[top].next=point;
    A[top].sen=A[point].sen;
   
    A[point].sen=top;//后继的前驱等于自己
   
    A[A[top].sen].next=top;//前驱的后继等于自己
   
    top++;
}

void DeleteOne(int key)
{
    int point=A[0].next;
    while(A[point].next!=-1)
    {
        if(A[point].key==key)
        {
            A[A[point].sen].next=A[point].next; //自己前驱的后继等于自己的后继
            A[A[point].next].sen=A[point].sen;  //自己后继的前驱等于自己的前驱
            return; //跳出函数
        }
        point=A[point].next;
    }
}

void print()
{
    int point=A[0].next;
    while(A[point].next!=-1)
    {
        fout<<A[point].key<<endl;
        point=A[point].next;
    }
}
void debug()
{
    for (int i=0;i<top;i++)
        fout<<i<<" "<<A[i].key<<" "<<A[i].sen<<" "<<A[i].next<<endl;
}

int GetMinElem()
{
    int point=A[0].next;
    return A[point].key;
}

int main()
{

    //Initialize
    A[0].key=-1;A[0].next=1;A[0].sen=-1;
    A[1].key=0Xfffffff;A[1].next=-1;A[0].sen=0;
    top=2;
    int num,key;
    fin>>num;
    for (int i=0;i<num;i++)
    {
        fin>>key;
        Insert(key);//插入一个关键字
    }
   
    int liu=num;
    int cost=0;
    //debug();
    while (liu!=1)
    {
        int m=GetMinElem();
        DeleteOne(m);
        int n=GetMinElem();
        DeleteOne(n);
        //fout<<m<<" "<<n<<endl<<endl;
        cost+=(m+n);
        Insert(m+n);
        liu--;
    }
    //print();
    fout<<cost<<endl;
    return 0;
}

<2>用小根堆寫的。
#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;
const int MAXN=0xfffffff;
int N;
int T;
int F[10001];


void init()
{
    scanf("%d\n",&N);
    T=N;
    for (int i=1;i<=N;i++)
        cin>>F[i];
}

void downshift(int i)
{
    int j,x;
    x=F[i];
    j=i<<1;
    while (j<=T)
    {
        if (F[j]>F[j+1] && j+1<=T)
            j++;
        if (x>F[j])
        {
            F[i]=F[j];
            i=j;
            j=j<<1;
        }
        else
            break;
    }
    F[i]=x;
}

void HeapWorks()
{
    long long ans=0;
    for (int i=N/2;i>=1;i--)
        downshift(i);
    int k;
    for (int i=1;i<=N-1;i++)
    {
        if (F[2]<F[3])
            k=2;
        else
            k=3;
        ans+=F[1]+F[k];
        F[1]+=F[k];
        downshift(1);
        F[1]=MAXN;
        downshift(1);
    }
    cout<<ans<<endl;
}

int main()

    freopen("fruit.in","r",stdin);
    freopen("fruit.out","w",stdout); 
    init();
    HeapWorks();
    return 0;
}

NOIP2007複賽提高組 矩陣取數遊戲 解題報告

3、矩陣取數遊戲 (game.pas/c/cpp)

【問題描述】
帥帥經常更同學玩一個矩陣取數遊戲:對於一個給定的n*m的矩陣,矩陣中的每個元素aij據爲非負整數。遊戲規則如下:
1. 每次取數時須從每行各取走一個元素,共n個。m次後取完矩陣所有的元素;
2. 每次取走的各個元素只能是該元素所在行的行首或行尾;
3. 每次取數都有一個得分值,爲每行取數的得分之和;每行取數的得分 = 被取走的元素值*2i,其中i表示第i次取數(從1開始編號);
4. 遊戲結束總得分爲m次取數得分之和。
帥帥想請你幫忙寫一個程序,對於任意矩陣,可以求出取數後的最大得分。
【輸入】
輸入文件game.in包括n+1行;
第一行爲兩個用空格隔開的整數n和m。
第2~n+1行爲n*m矩陣,其中每行有m個用單個空格隔開
【輸出】
輸出文件game.out僅包含1行,爲一個整數,即輸入矩陣取數後的最大的分。
【輸入輸出樣例1】


game.in
game.out
2 3
1 2 4
3 4 2
82
【輸入輸出樣例1解釋】
第1次:第一行取行首元素,第二行取行尾元素,本次的分為1*21+2*21=6
第2次:兩行均取行首元素,本次得分爲2*22+3*22=20
第3次:得分爲3*23+4*23=56。總得分爲6+20+56=82
【輸入輸出樣例2】

game.in
game.out
1 4
4 5 0 5
122

【輸入輸出樣例3】
game.in
game.out
2 10
96 56 54 46 86 12 23 88 80 43
16 95 18 29 30 53 88 83 64 67
316994

【限制】
60%的數據滿足:1<=n, m<=30,答案不超過1016
100%的數據滿足:1<=n, m<=80,0<=aij<=1000




動態規劃。由於每行是互不影響的,可以把每行孤立開看,對每行分別進行一次動態規劃計算。結果可能很大,需要高精度計算。爲避免計算高精度乘法,可以在初始化時把每個數的2^k遞推算出。
狀態設定
F[i,j] 爲當前行取前i個數和後j個數的最大得分。
V[i,j] 爲當前行第i個數乘以2^j的值。
狀態轉移方程
F[i,j]=max{ F[i-1,j] + V[i,p] , F[i,j-1] + V[M-j+1,p] } p=i+j(表示當前是第幾次取數)
V[i,j]=V[i,j-1]+V[i,j-1]
邊界條件
V[i,1]=當前行第i個數的值
F[0,0]=0
每行結果
Max{ F[i,M-i] } (0<=i<=M)
最終結果
每行結果的總和


【代碼】
由於數據很強,在我的電腦上的評測結果是:超時2組。
#include <iostream>
#include <string>
#include <cstring>
using namespace std;

const int SIZE=50;
class hugeint
{
public:
    unsigned int len;
    unsigned int num[SIZE];
    hugeint()
    {
        len=0;
        memset(num,0,sizeof(num));
    }
};

hugeint V[82][82];//V[i,j]為當前行第i個數乘以2^j的積
hugeint A[82];//A為讀入的矩陣的每一行
hugeint F[82][82];//F[i,j]為當前行取前i個數和後j個數的最大得分
hugeint tsum;
hugeint Two;
int N,M;//行數,列數

hugeint times(hugeint a,hugeint b)
{
    unsigned  int i,j;
    hugeint ans;
    memset(ans.num,0,sizeof(ans.num));
    for (i=1;i<=a.len;i++)
        for (j=1;j<=b.len;j++)
            ans.num[i+j-1]+=a.num[i]*b.num[j];
    for (i=1;i<=a.len+b.len;i++)
    {
        ans.num[i+1]+=ans.num[i]/10;
        ans.num[i]=ans.num[i]%10;
    }
    if (ans.num[a.len+b.len]>0)
        ans.len=a.len+b.len;
    else
        ans.len=a.len+b.len-1;
    return ans;
}

hugeint add(hugeint a,hugeint b)
{
    unsigned int i;
    hugeint ans;
    memset(ans.num,0,sizeof(ans.num));
    if (a.len>b.len)
        ans.len=a.len;
    else
        ans.len=b.len;
    for (i=1;i<=ans.len;i++)
    {
        ans.num[i]+=(a.num[i]+b.num[i]);
        ans.num[i+1]+=ans.num[i]/10;
        ans.num[i]%=10;
    }
    if (ans.num[ans.len+1]>0)
        ans.len++;
    return ans;
}

hugeint average(hugeint a,hugeint b)
{
    int i;
    hugeint ans;
    ans=add(a,b);
    for (i=ans.len;i>=2;i--)
    {
        ans.num[i-1]+=(ans.num[i]%2)*10;
        ans.num[i]/=2;
    }
    ans.num[i]/=2;
    if (ans.num[ans.len]==0)
        ans.len--;
    return ans;
}

bool over(hugeint a,hugeint b)
{
    int i;
    if (a.len<b.len)
        return false;
    if (a.len>b.len)
        return true;
    for (i=a.len;i>=1;i--)
    {
        if (a.num[i]<b.num[i])
            return false;
        if (a.num[i]>b.num[i])
            return true;
    }
    return false;
}

void PreDP()
{
    for (int i=1;i<=M;i++)
        V[i][0]=A[i];
    for (int i=1;i<=M;i++)
    {
        for (int j=1;j<=M;j++)
            V[i][j]=times(Two,V[i][j-1]);
    }
}

hugeint DP()
{
    hugeint sum;
    int p;
    for (int i=0;i<=M;i++)
    {
        for (int j=0;j<=M;j++)
        {
            hugeint m;
            p=i+j;
            if(p>M)
                continue;
            if (i-1>=0)  
            {
                if ( over( add(F[i-1][j],V[i][p]),m     )  )
                    m=add(F[i-1][j],V[i][p]);
            }
            if (j-1>=0)
            {
                if ( over( add(F[i][j-1],V[M-j+1][p]),m )  )
                    m=add(F[i][j-1],V[M-j+1][p]);
            }
            F[i][j]=m;
        }
    }
    for (int i=0;i<=M;i++)
    {
        if(  over(F[i][M-i],sum)  )
            sum=F[i][M-i];
    }
    return sum;
}

void init()
{
    Two.len=1;
    Two.num[1]=2;
    cin>>N>>M;
    char word[10]={'\0'};
    hugeint target;
    for (int i=1;i<=N;i++)
    {
        for (int j=1;j<=M;j++)
        {
            memset(word,'\0',sizeof(word));
            cin>>word;
            memset(target.num,0,sizeof(target.num));
            target.len=strlen(word);
            for (unsigned int k=1;k<=target.len;k++)
                target.num[k]=word[target.len-k]-'0';
            A[j]=target;
        }
        PreDP();
        tsum=add(tsum,DP());
    }
}

int main()
{
    freopen("game.in","r",stdin);
    freopen("game.out","w",stdout);
    init();
    for (int i=tsum.len;i>=1;i--)
        cout<<tsum.num[i];
    return 0;
}