NOIP演算法總結——關於簡單 線性動態規劃
動態規劃,顯然是一個很讓人頭疼的地方,也沒有個固定的演算法,最多就是有一些模板(比如揹包啊),要是想要增大做出來的機率,也就只好多做做題找找感覺了~
線性動態規劃可以說是DP中最簡單的型別了,當然裡面很多也是不容易的題目,也是需要一點技巧了。搞了好幾天了,現在就來總結一下。
一、演算法的選取,動態維護。
例題1:最大子段和
題目描述
給出一段序列,選出其中連續且非空的一段使得這段和最大。
輸入輸出格式
輸入格式:
輸入檔案maxsum1.in的第一行是一個正整數N,表示了序列的長度。
第2行包含N個絕對值不大於10000的整數A[i],描述了這段序列。
輸出格式:
輸入檔案maxsum1.out僅包括1個整數,為最大的子段和是多少。子段的最小長度為1。
輸入輸出樣例
輸入樣例#1:
7
2 -4 3 -1 2 -4 3
輸出樣例#1:
4
說明
【資料規模與約定】
對於40%的資料,有N ≤ 2000。
對於100%的資料,有N ≤ 200000。
【洛谷1115】
演算法一:暴力列舉。列舉每一個子段,求和,再求最大。
時間複雜度:列舉是O(n),每次計算是O(n),總共是O(n^3)。
#include <bits/stdc++.h>
using namespace std ;
int a[200010] ;
int main() {
int i, j, k, m, n ;
scanf ( "%d" , &n ) ;
for ( i = 1 ; i <= n ; i ++ )
scanf ( "%d", a+i ) ;
int ans = -2147483647 ;
for ( i = 1 ; i <= n ; i ++ )
for ( j = i ; j <= n ; j ++ ) {
int sum = 0 ;
for ( k = i ; k <= j ; k ++ )
sum += a[k] ;
ans = max ( ans, sum ) ;
}
printf ( "%d\n", ans ) ;
return 0 ;
}
顯然不可取。
看那範圍,顯然是TLE到乾乾淨淨。
演算法二:字首和優化。和演算法一類似,也是列舉每個子段,只是子段求和的時候使用字首和優化(O(1))。
時間複雜度:O(n^2)
#include <bits/stdc++.h>
using namespace std ;
int a[200010] ;
int main() {
int i, j, k, m, n ;
scanf ( "%d", &n ) ;
for ( i = 1 ; i <= n ; i ++ ) {
scanf ( "%d", a+i ) ;
a[i] += a[i-1] ;
}
int ans = -2147483647 ;
for ( i = 1 ; i <= n ; i ++ )
for ( j = i ; j <= n ; j ++ ) {
int sum = 0 ;
sum = a[j] - a[i-1] ;
ans = max ( ans, sum ) ;
}
printf ( "%d\n", ans ) ;
return 0 ;
}
啊!騙到了40分,還是可以的。
不過,顯然不是正解。
演算法三:動態維護最小字首和。
時間複雜度: O(n)
這還是要分析一下的……
對於i~j的子段,sum = a[j] - a[i-1] ;
那麼,以j為結尾的所有子段,都是a[j]減去一個什麼什麼東西。
顯然,減去的那個東西越小越好~
那麼,我們只要維護最小的那個就好了。所以時間降到了線性。
#include <bits/stdc++.h>
using namespace std ;
int a[200010] ;
int main() {
int i, j, k, m, n ;
scanf ( "%d", &n ) ;
for ( i = 1 ; i <= n ; i ++ ) {
scanf ( "%d", a+i ) ;
a[i] += a[i-1] ;
}
int ans = -2147483647, Min = 0 ;
for ( i = 1 ; i <= n ; i ++ ) {
ans = max ( ans, a[i] - Min ) ;
Min = min ( a[i], Min ) ;
}
printf ( "%d\n", ans ) ;
return 0 ;
}
沒話說,這還不AC?
二、字首和的利用
就像上面那個題目裡所使用的一樣,字首和經常能給我們很大的幫助。
那是一維的‘字首和’,其實,對於一些二維甚至三維的統計(不一定是求和,也可能是求 異或 之類),我們也可使用字首和。
最大正方形【洛谷1387】
題目描述
在一個n*m的只包含0和1的矩陣裡找出一個不包含0的最大正方形,輸出邊長。
輸入輸出格式
輸入格式:
輸入檔案第一行為兩個整數n,m(1<=n,m<=100),接下來n行,每行m個數字,用空格隔開,0或1.
輸出格式:
一個整數,最大正方形的邊長
輸入輸出樣例
輸入樣例#1:
4 4
0 1 1 1
1 1 1 0
0 1 1 0
1 1 0 1
輸出樣例#1:
2
分析:如果一個矩形的元素和與面積相等,那麼它裡面的元素就全部是1
演算法一:列舉
列舉正方形的某一個定點座標(O(n
時間複雜度:O(n^5)
顯然會TLE的乾乾淨淨。
由於這個暴力列舉很簡單,就不貼出程式碼了。
演算法二:還是列舉,只是加字首和優化
方法同上,但是求和的過程不是累加,而是使用字首和(O(1))。
怎麼使用字首和呢?
sum(i,j) 表示點(1,1)和點(i,j)圍成矩形裡元素的和。(如下圖)
那麼,為什麼要這樣表示呢?那是因為簡便計算矩陣和啊!
看上圖,假設我們要求計算的矩形(注意,這裡是可以推廣到一般情況的,矩形!)是圖示紅色方框所包括的部分(x1,y1)——(x2,y2)。
那麼,這個答案可以通過圖示 褐色方框 - 兩個青色方框 + 黑色方框(由於多減了一次) 得到。
所以,
這就是我們的計算式。
但是,怎麼計算sum[][]呢?
接下來的任務就很簡單了,直接上程式碼
#include <bits/stdc++.h>
using namespace std ;
const int maxn = 110 ;
bool a[maxn][maxn] ; // 記錄原始陣列
int sum[maxn][maxn] ; // sum意義同上所述
int main() {
int i, j, k, m, n, l ;
scanf ( "%d%d", &n, &m ) ;
for ( i = 1 ; i <= n ; i ++ )
for ( j = 1 ; j <= m ; j ++ )
scanf ( "%d", &a[i][j] ) ;
for ( i = 1 ; i <= n ; i ++ )
for ( j = 1 ; j <= m ; j ++ )
sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + a[i][j] ; // 遞推計算sum
int x2, y2, x1, y1 ;
int ans = -1000000000, L ; // 用於儲存最大面積,答案邊長
for ( x1 = 1 ; x1 <= n ; x1 ++ )
for ( y1 = 1 ; y1 <= m ; y1 ++ )
for ( l = 1 ; l+x1-1 <= n && l+y1-1 <= m ; l ++ ){
x2 = l+x1-1 ; y2 = l+y1-1 ;
int s = sum[x2][y2] - sum[x2][y1-1] - sum[x1-1][y2] + sum[x1-1][y1-1] ;
if ( s != l*l ) continue ;
if ( s > ans ) {
ans = s ;
L = l ;
}
}
printf ( "%d\n", L ) ;
return 0 ;
}
是不是顯然很簡單呢~
三、簡單的遞推。
例題1:過河【NOIp 2005 T2】
題目描述
在河上有一座獨木橋,一隻青蛙想沿著獨木橋從河的一側跳到另一側。在橋上有一些石子,青蛙很討厭踩在這些石子上。由於橋的長度和青蛙一次跳過的距離都是正整數,我們可以把獨木橋上青蛙可能到達的點看成數軸上的一串整點:0,1,……,L(其中L是橋的長度)。座標為0的點表示橋的起點,座標為L的點表示橋的終點。青蛙從橋的起點開始,不停的向終點方向跳躍。一次跳躍的距離是S到T之間的任意正整數(包括S,T)。當青蛙跳到或跳過座標為L的點時,就算青蛙已經跳出了獨木橋。
題目給出獨木橋的長度L,青蛙跳躍的距離範圍S,T,橋上石子的位置。你的任務是確定青蛙要想過河,最少需要踩到的石子數。
輸入輸出格式
輸入格式:
輸入檔案river.in的第一行有一個正整數L(1 <= L <= 10^9),表示獨木橋的長度。第二行有三個正整數S,T,M,分別表示青蛙一次跳躍的最小距離,最大距離,及橋上石子的個數,其中1 <= S <= T <= 10,1 <= M <= 100。第三行有M個不同的正整數分別表示這M個石子在數軸上的位置(資料保證橋的起點和終點處沒有石子)。所有相鄰的整數之間用一個空格隔開。
輸出格式:
輸出檔案river.out只包括一個整數,表示青蛙過河最少需要踩到的石子數。
輸入輸出樣例
輸入樣例#1:
10
2 3 5
2 3 5 6 7
輸出樣例#1:
2
說明
對於30%的資料,L <= 10000;
對於全部的資料,L <= 109。
分析:
To Be Continue … …
低價購買
尼克的任務
多米諾骨牌
合唱隊形
奇怪的字串