入門-動態規劃
- 1. [分梨](http://acm.zzu.edu.cn/problem.php?id=1163)
- 2. 最大公共子序列
- 3. [最大子段和](http://acm.hdu.edu.cn/showproblem.php?pid=1003)
- 4. [最大子陣和](http://acm.hdu.edu.cn/showproblem.php?pid=1081)
- 5. [母牛的故事](http://acm.hdu.edu.cn/showproblem.php?pid=2018)
- 6. [數塔](http://acm.hdu.edu.cn/showproblem.php?pid=2084)
- 7. [一隻小蜜蜂](http://acm.hdu.edu.cn/showproblem.php?pid=2044)
- 8. [折線分割平面](http://acm.hdu.edu.cn/showproblem.php?pid=2050)
- 9. [獻給杭電五十週年校慶的禮物](http://acm.hdu.edu.cn/showproblem.php?pid=1290)
1. 分梨
題目描述
小明非常喜歡吃梨,有一天他得到了ACMCLUB送給他的一筐梨子。由於他比較仗義,就打算把梨子分給好朋友們吃。現在他要把M個梨子放到N個盤子裏面(我們允許有的盤子爲空),你能告訴小明有多少種分法嗎?(請注意,例如有三個盤子,我們將5,1,1和1,1,5,視爲同一種分法)
輸入
輸入包含多組測試樣例。每組輸入的第一行是一個整數t。
接下來t行,每行輸入兩個整數M和N,代表有M個梨和N個盤子。(M和N均大於等於0)
輸出
對於每對輸入的M和N,輸出有多少種方法。
樣例輸入
1
7 3
樣例輸出
8
解析 遞歸分配
M個梨, N個盤子的分法
dp(m, n){
if (只有一個盤子 || 沒有梨)
只有一種分法;
if (盤子N比梨M多)
則最多用M個盤子, dp(m,n) = dp(m,m);
else
如果每個至少一個梨, 則dp(m-n, n)
如果有一個盤子爲空, 則dp(m, n-1)
dp(m, n) = dp(m, n-1) + dp(m-n, n)
}
AC
#include<stdio.h>
int c(int x,int y)
{
if(y == 1 || x == 0)
return 1;
if(x<y)
return c(x,x);
else
return c(x,y-1)+c(x-y,y);
}
int main()
{
int t,n,m;
while(scanf("%d",&t)!=EOF)
{
while(t--)
{
scanf("%d%d",&m,&n);
printf("%d\n",c(m,n));
}
}
return 0;
}
2. 最大公共子序列
3. 最大子段和
Problem Description
Given a sequence a[1],a[2],a[3]…a[n], your job is to calculate the max sum of a sub-sequence. For example, given (6,-1,5,4,-7), the max sum in this sequence is 6 + (-1) + 5 + 4 = 14.
Input
The first line of the input contains an integer T(1<=T<=20) which means the number of test cases. Then T lines follow, each line starts with a number N(1<=N<=100000), then N integers followed(all the integers are between -1000 and 1000).
Output
For each test case, you should output two lines. The first line is “Case #:”, # means the number of the test case. The second line contains three integers, the Max Sum in the sequence, the start position of the sub-sequence, the end position of the sub-sequence. If there are more than one result, output the first one. Output a blank line between two cases.
Sample Input
2
5 6 -1 5 4 -7
7 0 6 -1 1 -6 7 -5
Sample Output
Case 1:
14 1 4
Case 2:
7 1 6
解析
sum, maxsum = M[n-1]
for auto m : M[]:
sum = max(sum+m, m)
if sum >= maxsum:
maxsum = sum
AC
#include <iostream>
#include <cstring>
using namespace std;
int M[100005], arr[100005];
int l, r;
int maxSubArr(int D[], int n){
int i, sum = 0, maxsum = D[n-1];
l = n-1;
for (i=n-1; i>=0; i--){
if (sum > 0){
sum = sum + D[i];
}else{
sum = D[i];
}
if (sum >= maxsum) { //If there are more than one result, output the first one.
maxsum = sum;
l = i;
}
}
return maxsum;
}
int main(){
int T, N;
cin>>T;
for (int t=1; t<=T; t++){
cin>>N;
for (int i=0; i<N; i++){
cin>>M[i];
}
int ans = maxSubArr(M, N);
cout<<"Case "<<t<<":"<<endl;
int s = 0;
for (int i = l; i<N; i++){
s += M[i];
if (s == ans){
r=i;
break;
}
}
cout<<ans<<" "<<l+1<<" "<<r+1<<endl;
if (t<T){
cout<<endl;
}
}
return 0;
}
4. 最大子陣和
Problem Description
Given a two-dimensional array of positive and negative integers, a sub-rectangle is any contiguous sub-array of size 1 x 1 or greater located within the whole array. The sum of a rectangle is the sum of all the elements in that rectangle. In this problem the sub-rectangle with the largest sum is referred to as the maximal sub-rectangle.
As an example, the maximal sub-rectangle of the array:
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
is in the lower left corner:
9 2
-4 1
-1 8
and has a sum of 15.
Input
The input consists of an N x N array of integers. The input begins with a single positive integer N on a line by itself, indicating the size of the square two-dimensional array. This is followed by N 2 integers separated by whitespace (spaces and newlines). These are the N 2 integers of the array, presented in row-major order. That is, all numbers in the first row, left to right, then all numbers in the second row, left to right, etc. N may be as large as 100. The numbers in the array will be in the range [-127,127].
Output
Output the sum of the maximal sub-rectangle.
Sample Input
4
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
Sample Output
15
解析
這一題可以藉助上一題的思路,把求最大子陣轉化爲求最大子段和,也就是變成一維情況。
(以第一行最爲開始)先求第一行的最大和;將第二行數據加到第一行,再求此時的最大值;再將下一行加上去,求最大值.....;
.最終得到第一列到最後一列的最大值;還要計算第二行到最後一行的最大和,第三行到最後一行的最大和;
fun(){
//求最大子段和
}
for i = 0 : N #第i行到j行最大子陣和
# 第一次循環結束,我們得到的是第一行到第N行的N x M的最大子陣和(N是行數,M是列數,M由fun()決定)
# 第二次循環結束,我們得到的是第二行到第N行的N-1 x M的最大子陣和
for j = i : N
把第j行加到array上:arry[k] = M[j][k]
計算array的最大子段和:fun(array)
AC
#include <iostream>
#include <cstring>
using namespace std;
int M[105][105], arr[105];
int maxSubArr(int D[], int n){
int sum=0, maxsum=D[0];
for (int i=0; i<n; i++){
if (sum>0){
sum = sum + D[i];
}else{
sum = D[i];
}
if (sum > maxsum) maxsum = sum;
}
return maxsum;
}
int main(){
int N;
while(scanf("%d", &N) != EOF){//注意輸入
for (int i=0; i<N; i++){
for (int j=0; j<N; j++){
cin>>M[i][j];
}
}
int maxsubrec = M[0][0];
for (int i=0; i<N; i++){
memset(arr, 0, sizeof(arr));
for (int j=i; j<N; j++){
for (int k=0; k<N; k++){
arr[k] += M[j][k];
}
int maxsubarr = maxSubArr(arr, N);
if (maxsubarr > maxsubrec) maxsubrec = maxsubarr;
}
}
cout<<maxsubrec<<endl;
}
return 0;
}
5. 母牛的故事
Problem Description
有一頭母牛,它每年年初生一頭小母牛。每頭小母牛從第四個年頭開始,每年年初也生一頭小母牛。請編程實現在第n年的時候,共有多少頭母牛?
Input
輸入數據由多個測試實例組成,每個測試實例佔一行,包括一個整數n(0<n<55),n的含義如題目中描述。
n=0表示輸入數據的結束,不做處理。
Output
對於每個測試實例,輸出在第n年的時候母牛的數量。
每個輸出佔一行。
Sample Input
2
4
5
0
Sample Output
2
4
6
解析
f(n) = f(n-1) + f(n-3)
AC
#include<stdio.h>
//遞歸
int sum(int n)
{
if (n <= 4) return n;
else return (sum(n-1) + sum(n - 3));
}
int main()
{
int n;
while (~scanf("%d", &n) != EOF&&n!=0)
printf("%d\n", sum(n));
getchar();
return 0;
}
6. 數塔
Problem Description
在講述DP算法的時候,一個經典的例子就是數塔問題,它是這樣描述的:
有如下所示的數塔,要求從頂層走到底層,若每一步只能走到相鄰的結點,則經過的結點的數字之和最大是多少?
已經告訴你了,這是個DP的題目,你能AC嗎?
Input
輸入數據首先包括一個整數C,表示測試實例的個數,每個測試實例的第一行是一個整數N(1 <= N <= 100),表示數塔的高度,接下來用N行數字表示數塔,其中第i行有個i個整數,且所有的整數均在區間[0,99]內。
Output
對於每個測試實例,輸出可能得到的最大和,每個實例的輸出佔一行。
Sample Input
1
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
Sample Output
30
解析
遞推公式:DP[i][j] = M[i][j] + max(DP[i+1][j], DP[i+1][j+1])
自底向上計算到塔頂,得到的結果即爲最大和
AC
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int main()
{
int a[105][105];
int t, n, i, j;
while(cin>>t)
{
while(t--)
{
cin>>n;
memset(a,0,sizeof(a));
for(i=0; i<n; i++)
for(j=0; j<=i; j++)
cin>>a[i][j];
for(i=n-2; i>=0; i--)
for(j=0; j<=i; j++)
a[i][j] = a[i][j] + max(a[i+1][j], a[i+1][j+1]);
cout<<a[0][0]<<endl;
}
}
return 0;
}
7. 一隻小蜜蜂
Problem Description
有一只經過訓練的蜜蜂只能爬向右側相鄰的蜂房,不能反向爬行。請編程計算蜜蜂從蜂房a爬到蜂房b的可能路線數。
其中,蜂房的結構如下所示。
Input
輸入數據的第一行是一個整數N,表示測試實例的個數,然後是N 行數據,每行包含兩個整數a和b(0<a<b<50)。
Output
對於每個測試實例,請輸出蜜蜂從蜂房a爬到蜂房b的可能路線數,每個實例的輸出佔一行。
Sample Input
2
1 2
3 6
Sample Output
1
3
解析
思路1:局部最優解得到全局最優解
蜂房5路線 = 蜂房3的路線 + 蜂房4的路線
蜂房6路線 = 蜂房4的路線 + 蜂房5的路線
。。。
蜂房n路線 = 蜂房n-2的路線 + 蜂房n-1的路線
斐波那契數列
思路2:枚舉
1->2 (1種):1->2;
1->3 (2種): 1->2->3; 1->3;
1->4 (3種): 1->2->4; 1->2->3->4; 1->3->4;
1->5 (5種): 1->2->3->5; 1->3->5; 1->2->4->5; 1->2->3->4->5; 1->3->4->5;
1->6 (8種): 1->4(3種)->6; 1->5(5種)->6;
得斐波那契數列
唯一要注意的就是數比較大,得用long long類型
AC
#include <iostream>
using namespace std;
int main()
{
long long ans = 0, pre = 0;
int T;
cin>>T;
while(T--){
int a, b;
cin>>a>>b;
for (int i=2; i <= b-a+1; i++){
if (i==2) {
pre = 0;
ans = 1;
}
else if (i==3) {
pre = 1;
ans = 2; continue;
}else{
ans = pre + ans;
pre = ans - pre;
}
}
cout << ans << endl;
}
return 0;
}
8. 折線分割平面
Problem Description
我們看到過很多直線分割平面的題目,今天的這個題目稍微有些變化,我們要求的是n條折線分割平面的最大數目。比如,一條折線可以將平面分成兩部分,兩條折線最多可以將平面分成7部分,具體如下所示。
Input
輸入數據的第一行是一個整數C,表示測試實例的個數,然後是C 行數據,每行包含一個整數n(0<n<=10000),表示折線的數量。
Output
對於每個測試實例,請輸出平面的最大分割數,每個實例的輸出佔一行。
Sample Input
2
1
2
Sample Output
2
7
解析
n條直線最多分平面問題
題目大致如:n條直線,最多可以把平面分爲多少個區域。
析:可能你以前就見過這題目,這充其量是一道初中的思考題。但一個類型的題目還是從簡單的入手,才容易發現規律。
當有n-1條直線時,平面最多被分成了f(n-1)個區域。
則第n條直線要是切成的區域數最多,就必須與每條直線相交且不能有同一交點。
這樣就會得到n-1個交點。
這些交點將第n條直線分爲2條射線和n-2條線段。
而每條射線和線斷將已有的區域一分爲二。這樣就多出了2+(n-2)個區域。
故:f(n)=f(n-1)+n
=f(n-2)+(n-1)+n
……
=f(1)+1+2+……+n
=n(n+1)/2+1
根據直線分平面可知,由交點決定了射線和線段的條數,進而決定了新增的區域數。
當n-1條折線時,區域數爲f(n-1)。
爲了使增加的區域最多,則折線的兩邊的線段要和n-1條折線的邊,即2*(n-1)條線段相交。
那麼新增的線段數爲4*(n-1),射線數爲2。
但要注意的是,折線本身相鄰的兩線段只能增加一個區域。
故:f(n)=f(n-1)+4(n-1)+2-1
=f(n-1)+4(n-1)+1
=f(n-2)+4(n-2)+4(n-1)+2
……
=f(1)+4+4*2+……+4(n-1)+(n-1)
=2n^2-n+1
AC
#include<iostream>
using namespace std;
int main()
{
int num,k;
cin>>num;
while(num--)
{
cin>>k;
cout<<2*k*k-k+1<<endl;
}
return 0;
}
9. 獻給杭電五十週年校慶的禮物
Problem Description
今年是我們杭電建校五十週年,這是一個值得祝福的日子。我們該送給母校一個怎樣的禮物呢?對於目前的大家來說,最好的禮物當然是省賽中的好成績,我不能參賽,就送給學校一個DOOM III球形大蛋糕吧,這可是名牌,估計要花掉我半年的銀子呢。
想象着正式校慶那一天,校長親自操刀,把這個大蛋糕分給各地趕來祝賀的校友們,大家一定很高興,呵呵,流口水了吧…
等一等,吃蛋糕之前先考大家一個問題:如果校長大人在蛋糕上切了N刀(校長刀法極好,每一刀都是一個絕對的平面),最多可以把這個球形蛋糕切成幾塊呢?
做不出這個題目,沒有蛋糕吃的!
爲-了-母-校-,爲-了-蛋-糕-(不是爲了DGMM,楓之羽最會浮想聯翩…),加-油-!
Input
輸入數據包含多個測試實例,每個實例佔一行,每行包含一個整數n(1<=n<=1000),表示切的刀數。
Output
對於每組輸入數據,請輸出對應的蛋糕塊數,每個測試實例輸出一行。
Sample Input
1
2
3
Sample Output
2
4
8
解析
平面分割空間問題
由二維的分割問題可知,平面分割與線之間的交點有關,即交點決定射線和線段的條數,從而決定新增的區域數。
試想在三維中則是否與平面的交線有關呢?
當有n-1個平面時,分割的空間數爲f(n-1)。
要有最多的空間數,則第n個平面需與前n-1個平面相交,且不能有共同的交線。即最多有n-1 條交線。
而這n-1條交線把第n個平面最多分割成g(n-1)個區域。
(g(n)爲(1)中的直線分平面的個數 )此平面將原有的空間一分爲二,則最多增加g(n-1)個空間。
故:f=f(n-1)+g(n-1) ps:g(n)=n(n+1)/2+1
=f(n-2)+g(n-2)+g(n-1)
……
=f(1)+g(1)+g(2)+……+g(n-1)
=2+(1*2+2*3+3*4+……+(n-1)n)/2+(n-1)
=(1+2^2+3^2+4^2+……+n^2-1-2-3-……-n )/2+n+1
=(n^3+5n)/6+1
AC
#include <stdio.h>
int main(){
int n;
while(scanf("%d", &n)!=EOF){
printf("%d\n", (n*n*n+5*n)/6+1);
}
return 0;
}