DP(类似于矩阵链乘法)
我们以sample来说明问题
长度100,切的地方为25,50,75.
那么无论怎么切最后都是剩下四段 [0,25] , [25,50] , [50,75] , [75,100]
把它们连续的两两合并的话就能得到上一层的状态,好像[0,25]和[25,50]合并,就得到[0,50],也就是说从[0,50]变到[0,25] , [25,50],花费是多少呢,就是50
所以说到这里,也差不多了,状态转移方程为 dp[i][j]=min{ dp[i][k]+dp[k][j]+(y-x)} (k是i和j之间的一个数)
这样子的话,我们的目标状态就是dp[0][len],即原来的整一条的费用。
这个时候无论是用递推还是用记忆化搜索去做都非常形象,非常好懂。
另外dp[i][j]里面的ij表示的确切的长度,而数据中木棒的长度上限为1000,所以dp数组大小要为dp[1000][1000]。当然可以优化一下空间,同时也提高了一些时间
dp[i][j]的i和j表示数据中的第i刀和第j刀,这样其实也起到了表示长度的作用,因为数据说明中说了切的地方是严格递增的序列。所以dp数组的大小只需要开到dp[50][50] , 因为最多切50刀
记忆化搜索(0.500s)
#include <cstdio> #include <cstring> #define M 60 #define N 1010 #define INF 0x3f3f3f3f int a[M],dp[N][N]; int n,m; int min(int i ,int j) { return i<j?i:j; } void dfs(int x , int y) { if(dp[x][y]!=INF) return ; for(int i=1; a[i]<y; i++) if(a[i]>x) { dfs(x,a[i]); dfs(a[i],y); dp[x][y]=min(dp[x][y] , dp[x][a[i]]+dp[a[i]][y]+(y-x) ); } return ; } int main() { while(scanf("%d",&n)!=EOF && n) { scanf("%d",&m); a[0]=0; for(int i=1; i<=m; i++) scanf("%d",&a[i]); a[m+1]=n; //memset(dp,0x3f,sizeof(dp)); for(int i=0; i<=m; i++) dp[a[i]][a[i+1]]=0; for(int l=2; l<=m+1; l++) for(int i=0; i<=m-l+1; i++) dp[a[i]][a[i+l]]=INF; dfs(0,n); printf("The minimum cutting is %d.\n",dp[0][n]); } return 0; }
递推(0.108s)
#include <cstdio> #include <cstring> #define M 60 #define N 1010 #define INF 0x3f3f3f3f int a[M],dp[N][N]; int n,m; int min(int i ,int j) { return i<j?i:j; } int main() { while(scanf("%d",&n)!=EOF && n) { scanf("%d",&m); a[0]=0; a[m+1]=n; for(int i=1; i<=m; i++) scanf("%d",&a[i]); //memset(dp,0x3f,sizeof(dp)); //并不是所有状态都要清INF,否则会TLE for(int i=0; i<=m; i++) dp[a[i]][a[i+1]]=0; for(int l=2; l<=m+1; l++) //把多少块合并 for(int i=0; i<=m-l+1; i++) //标号 { int j=i+l , x=a[i] , y=a[j]; dp[x][y]=INF; for(int k=i+1; k<j; k++) dp[x][y]=min(dp[x][y] , dp[x][a[k]]+dp[a[k]][y]+y-x); } printf("The minimum cutting is %d.\n",dp[0][n]); } return 0; }
递推优化加速(0.072s)(只是修改了一些微小的地方,思想和上面的递推是完全一样的)
#include <cstdio> #include <cstring> #define M 60 #define N 1010 #define INF 0x3f3f3f3f int a[M],dp[M][M]; int n,m; int min(int i ,int j) { return i<j?i:j; } int main() { while(scanf("%d",&n)!=EOF && n) { scanf("%d",&m); a[0]=0; a[m+1]=n; for(int i=1; i<=m; i++) scanf("%d",&a[i]); //memset(dp,0x3f,sizeof(dp)); //并不是所有状态都要清INF,否则会TLE for(int i=0; i<=m; i++) dp[i][i+1]=0; for(int l=2; l<=m+1; l++) //把多少块合并 for(int i=0; i<=m-l+1; i++) //标号 { int j=i+l,Min=INF; for(int k=i+1; k<j; k++) Min=min(Min , dp[i][k]+dp[k][j]); dp[i][j]=Min+a[j]-a[i]; } printf("The minimum cutting is %d.\n",dp[0][m+1]); } return 0; }