1. 程式人生 > >[最大費用最大流] [記憶化搜尋] [Vijos P1653] 瘋狂的方格取數 (getnum)

[最大費用最大流] [記憶化搜尋] [Vijos P1653] 瘋狂的方格取數 (getnum)

背景 Background

Due to the talent of talent123,當talent123做完NOIP考了兩次的二取方格數和vijos中的三取方格數後,突發奇想….

題目描述 Description

在一個寬M,長N的矩陣中,請你編一個程式,n次從矩陣的左上角走到矩陣的右下角,每到一處,就取走該處的數字,請你選擇一
種走法使取得的數字的和最大,並輸出其最大值。其中:3<=M<=20,M<=N<=100,1<=n<=10
如輸入資料:
3 10 13
0 1 2 3 4 9 7 1 3 1
9 1 2 2 3 6 7 8 1 2
1 2 3 4 5 9 8 7 6 1
9 7 1 3 1 9 1 2 2 3
6 7 8 1 2 1 2 3 4 5
9 1 2 2 3 6 7 8 1 2
1 2 3 4 5 9 8 7 6 1
9 7 1 3 1 9 1 2 2 3
6 7 8 1 2 1 2 3 4 5
9 1 2 2 3 6 7 8 1 2
1 2 3 4 5 9 8 7 6 1
9 7 1 3 1 9 1 2 2 3
6 7 8 1 2 1 2 3 4 0
其中n

=3,M=10,N=13
即當n=3時,就相當於是3取方格數。
對於以上的資料:
將輸出:297
//注:如過你想到了無記憶性搜所的方法(不管你怎樣優化),你可以直接放棄這道題了。
//提示1:動態規劃如果用的是二位陣列,規模為100100000即可。
//提示2:如果你堅信自己的程式已經無可優化了,可有2個數據依然超時,那麼告訴你,存在資料有M<n的情況!!!

輸入 Input

第一行:三個整數:n,M,N
以下的N行每行M個數字,代表你要處理的矩陣。

輸出 Output

只有一行:你所取得的數字的和。

樣例輸入 Sample Input

4 6 7
0 2 3 4 5 6
6 5 4 3 2 1
0 9 8 7 6 5
12 3 4 5 6 7
0 0 0 1 2 3
12 23 34 45 1 23
4 5 6 6 1 0

樣例輸出 Sample Output

265

限制 Limits

資料範圍見題目
共有10個測試資料,每個測試資料包含1個測試點,每個測試點的時間限制為2秒鐘。
Time Limit :2s & Memory Limit : 128MB

來源 Source

本題目來自:北京市,中關村中學,高三9班,孫一(網名:talent123),聯絡方式:865383864(QQ)

這道題是PoPoQQQ在培訓時講的,直接上最大費用最大流,模板水過……
思路:拆點建邊,一個格子拆成兩個點,這兩點之間有一條流量為1,權值為matrix[i][j]的邊,向四周格子連流量為INT_MAX,權值為0

的邊。最後超級源S(1,1)連流量為n,權值為0的邊,(N,M)向超級匯連流量為n,權值為0的邊。
連邊注意不要溢位範圍……(別連到外面去)
還有超級源和超級匯的選擇
然後就是程式碼了……
Code

題解貌似是記憶化搜尋?

#include <stdio.h>
#include <math.h>
int move[100][100000]={};//move[step][status];//status是一個 M進位制, time位 的狀態儲存量;
int time,map[100][20],M,N;//橫 M <20,豎 N<100,M<N,規模:M+N<=100,time<=10,((2M)^time)*(M+N)<=10000000;
int trial;//trial=2^time; //trial是一個 2進位制,time位 的增量儲存變數 ;
int fp(int step,int status)
{
    if(move[step][status]!=0)return move[step][status];//曾經已經求出過此狀態的值 ;
    int temp=0,max;//temp:此狀態下,覆蓋的值!   max:從此開始(包括此狀態)一直到終止狀態的最大值,賦temp為初值!;
    int flag[100][20]={};//一個位置的值是否被取過了;
    int rem=status,i;
    int list[10];
    for(i=0;i<time;i++)//計算:temp;
    {
        int x=rem%M,y=step-x;   //翻譯 ;
        list[i]=x;
        if(flag[y][x]==0)  //之前這個地方沒有被取過
        {
            temp+=map[y][x];
            flag[y][x]=1;
        }
        rem/=M;
    } //for
    max=temp;
    for(i=0;i<trial;i++)//列舉下一層搜尋中所有單位的 (0--1)橫向增量狀態:i;
    {
        //計算新編碼;
        int flag2=0;//flag==0則新編碼合法,flag==1則新編碼越界;
        int p=i,j;
        int new_stitus=0;//新狀態編碼;
        for(j=0;j<time;j++)//對於增量編碼 p(from:i),計算新的狀態編碼 ;
        {
            int increase=p%2;
            if( (increase==0?(step-list[time-j-1]+1):(list[time-j-1]+1))>=(increase==0?N:M) )//是否越界?
            {//如果越界 ;
                flag2=1;//標誌越界 ;
                break;
            }
            //轉錄 ;
            new_stitus*=M;
            new_stitus+=list[time-j-1]+increase;
            p=p/2;
        }//for
        if(flag2==0&&max<temp+fp(step+1,new_stitus))//不越界
        {
            max=temp+fp(step+1,new_stitus);//更新 max 的值(同時進行狀態的轉移)
        }
    }//for
    move[step][status]=max;//儲存此狀態的結果 ;
    return max;
}
int main(void)
{
    int i,j;
    scanf("%d%d%d",&time,&M,&N);
    if(M<time)time=M;
    //壓縮(當M<time時,更快的方法是直接輸出map上所有數的簡單相加和,
    //由於資料規模比2S運算量小一個數量級,故這樣也可以得滿分);
    for(i=0;i<N;i++)
       for(j=0;j<M;j++)
            scanf("%d",&map[i][j]);
    trial=(int)pow(2,time);//0---(trail-1)即所有的0--1狀態 ;
    printf("%d",fp(0,0));
    return 0;
}