題目描述
為了提高智商,ZJY開始學習組合數學。某一天她解決了這樣一個問題:給一個網格圖,其中某些格子有財寶。每次從左上角出發,只能往右或下走。問至少要走幾次才可能把財寶全撿完。
但是她還不知足,想到了這個問題的一個變形:假設每個格子中有好多塊財寶,而每一次經過一個格子至多隻能撿走一塊財寶,其他條件不變,至少要走幾次才可能把財寶全撿完?
這次她不會做了,你能幫幫她嗎?
輸入輸出格式
輸入格式:
第一行為一個正整數t,表示資料組數
每組資料的第一行是兩個正整數n和m,表示這個網格圖有n行m列。
接下來n行,每行m個非負整數,表示這個格子中的財寶數量(0表示沒有財寶)。
輸出格式:
對於每組資料,輸出一個整數,表示至少走的次數。
輸入輸出樣例
說明
資料範圍
對於30%的資料,n≤5.m≤5,每個格子中的財寶數不超過5塊。
對於50%的資料,n≤100,m≤100,每個格子中的財寶數不超過1000塊
對於100%的資料,n≤1000,m≤1000,每個格子中的財寶不超過10^6塊
解析:
這是天津市2015年的省選題(天津好像離北京很近~~~)於是乎我就做了這道題。。。
題目的意思就是用最少的鏈覆蓋住整個圖,自然而然(看過題解後)就想到了最小鏈覆蓋;
而根據著名的(反正我是沒聽過)的Dilworth定理,DAG最小鏈覆蓋的條數就等於最大點獨立集/最長反鏈的元素個數;;;
所謂de反鏈就是去找一些點,其中找兩個點,使其直接間接均不可達;而反鏈中元素最多的就是最長反鏈了。
所以這個題的本質上是去找這個圖的最長反鏈。
接下來就到了我們有趣的dynamic programming環節辣;;;
由題目可知,只能從左往右,從上往下走,所以這個題的反鏈一定是由右上到左下的。
而從上面和右面均能形成鏈,所以直接它直接就可能是這個點的最長反鏈;
從右上走到左下是不可能的,所以我們要把右上的點加上這個點的值才可能是最長反鏈;
而我們要求的是最長反鏈,但這幾個部分顯然是不能夠同時取的,所以要選這三個的最大值
狀態轉移方程就很容易寫出了:
f[i][j]=max(f[i-1][j+1]+a[i][j],max(f[i-1][j],f[i][j+1]));
這裡還要注意的一點就是迴圈變數,i是從1-〉n遞增的,j是從m-〉1遞減的,原因很簡單,就是要滿足最長反鏈從左下不可能到達右上的性質;
下面上程式碼:
#include<iostream>
#include<cstdio>//getchar要引cstdio
using namespace std;
int t,n,m;
long long a[1005][1005],f[1005][1005];//要開大!洛谷上本題有5個極限資料,不開就會RE得很慘~
int read()//讀入優化
{
int f=1,x=0;
char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
return x*f;
}
int main()
{
t=read();
for(int s=1;s<=t;s++)
{
n=read();
m=read();
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
a[i][j]=read();
f[i][j]=0;
}
}
//dp
for(int i=1;i<=n;i++)
{
for(int j=m;j>=1;j--)
{
f[i][j]=max(f[i-1][j+1]+a[i][j],max(f[i-1][j],f[i][j+1]));
}
}
cout<<f[n][1]<<endl;//千萬不要忘記輸出回車。。。我爆了5次零。。。
}
}