1. 程式人生 > 實用技巧 >小A點菜

小A點菜

目錄

傳送門

思路\(1\)

十分經典的一道DP題。
轉移方程式為:

f[j]+=f[j-a[i]];

我們首先定義\(f[i][j]\)表示前\(i\)個菜品恰好花費\(j\)元的方案數。
當我們買第\(i\)道菜時:

f[i][j]+=f[i-1][j-a[i]];

當我們不買第\(i\)道菜時:

f[i][j]+=f[i-1][j];

但當第\(i\)道菜的價格被就是\(j\)時,就可以只買自己,這種情況應該其實包含在\(1\)中,但是由於\(f[i][0]=0\),所以不會算入這種情況。所以我們就要把\(f[i][0]\)剛開始就賦值為\(1\)
程式碼

#include <bits/stdc++.h>
using namespace std;
int n,m,a[105],f[105][10005];

int main() {
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=0;i<=n;i++) f[i][0]=1;//注意0 
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=m;j++) {
			f[i][j]+=f[i-1][j];
			if(j>=a[i]) f[i][j]+=f[i-1][j-a[i]];
		}
	}
	printf("%d",f[n][m]);
	return 0;
}

我們可以優化為一維。
但我們的第二重迴圈必須逆序跑,因為我們一維的轉移方程式為:

f[j]+=f[j-a[i]];

如果跑正序,我們的\(f[j]\)還沒有更新,\(f[j-a[i]]\)反倒先更新了,這樣的話與題意的“每種菜只有一份”相沖。如果沒有這句話,我們就可以正序了。

#include <bits/stdc++.h>
using namespace std;
int n,m,a[105],f[10005];

int main() {
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	f[0]=1; 
	for(int i=1;i<=n;i++) {
		for(int j=m;j>=a[i];j--)
			f[j]+=f[j-a[i]];
	}
	printf("%d",f[m]);
	return 0;
}

思路\(2\)

\((1)if(j==第i道菜的價格)\ f[i][j]=f[i-1][j]+1;\)

\((2)if(j>第i道菜的價格)\ f[i][j]=f[i-1][j]+f[i-1][j-第i道菜的價格];\)

\((3)if(j<第i道菜的價格)\ f[i][j]=f[i-1][j];\)

說的簡單一些,這三個方程,每一個都是在吃與不吃之間抉擇。若錢充足,辦法總數就等於吃這道菜的辦法數與不吃這道菜的辦法數之和;若不充足,辦法總數就只能承襲吃前\(i-1\)道菜的辦法總數。依次遞推,在最後,我們只要輸出\(f[n][m]\)的值即可。

程式碼:

#include <bits/stdc++.h>
using namespace std;
int n,m,a[105],f[105][10005];

int main() {
	scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++) {
    	for(int j=1;j<=m;j++) {
			if(j==a[i])f[i][j]=f[i-1][j]+1;
        	else if(j>a[i]) f[i][j]=f[i-1][j]+f[i-1][j-a[i]];
        	else f[i][j]=f[i-1][j];
    	}
	}
	printf("%d",f[n][m]);
    return 0;
}