1.直线取石子
#include <stdio.h> #include <memory.h> #include <math.h> #include <string> #include <vector> #include <set> #include <stack> #include <queue> #include <algorithm> #include <map> #define I scanf #define OL puts #define O printf #define F(a,b,c) for(a=b;a<c;a++) #define FF(a,b) for(a=0;a<b;a++) #define FG(a,b) for(a=b-1;a>=0;a--) #define LEN 100 #define MAX 1<<30 #define V vector<int> using namespace std; const int n=6; int a[n]={1,5,6,7,3,2}; int sum[n]; int dp[n][n]; int getmin(){ int i,j,v,k; F(v,1,n){ FF(i,n-v){ j=i+v; int tmp=sum[j]-((i>0)?sum[i-1]:0); dp[i][j]=MAX; F(k,i,j){ dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+tmp); } } } return dp[0][n-1]; } int main(){ memset(dp,0,sizeof(dp)); sum[0]=a[0]; int i; F(i,1,n){ sum[i]=sum[i-1]+a[i]; } printf("%d ",getmin()); return 0; }
i:开始下标
j:结束下标
k:区间下标,在【i,j)区间进行循环
2.圆形取石子
我按照直线取石子的方法在OJ:https://www.luogu.org/problemnew/show/P1880 中无法AC,然后研读了博客 http://blog.csdn.net/acdreamers/article/details/18039073 的代码:
博客代码:
#include <iostream> #include <string.h> #include <stdio.h> using namespace std; const int INF = 1 << 30; const int N = 205; int mins[N][N]; int maxs[N][N]; int sum[N],a[N]; int minval,maxval; int n; int getsum(int i,int j) //[i,i+j] { //[i,n-1] + [0, (i+j)%n) ] if(i+j >= n) return getsum(i,n-i-1) + getsum(0,(i+j)%n); else return sum[i+j] - (i>0 ? sum[i-1]:0); } void Work(int a[],int n) { for(int i=0;i<n;i++) mins[i][0] = maxs[i][0] = 0; for(int j=1;j<n;j++) { for(int i=0;i<n;i++) { int s=i,e=i+j; int delta=getsum(i,j);//sum∈[i , i+j ] mins[i][j] = INF; maxs[i][j] = 0; for(int k=0;k<j;k++) //在取石子数目上进行优化 { mins[i][j] = min(mins[i][j], //从i开始,取k颗 从i+k+1开始,取j-k-1颗 mins[i][k] + mins[(i+k+1)%n][j-k-1] + delta); maxs[i][j] = max(maxs[i][j], maxs[i][k] + maxs[(i+k+1)%n][j-k-1] + delta); } } } minval = mins[0][n-1]; maxval = maxs[0][n-1]; for(int i=0;i<n;i++) { minval = min(minval,mins[i][n-1]); maxval = max(maxval,maxs[i][n-1]); } } int main() { freopen("D:/CbWorkspace/动态规划/石子合并.txt","r",stdin); while(scanf("%d",&n)!=EOF) { for(int i=0;i<n;i++) scanf("%d",&a[i]); sum[0] = a[0]; for(int i=1;i<n;i++) sum[i] = sum[i-1] + a[i]; Work(a,n); printf("%d %d ",minval,maxval); } return 0; }
循环情况:
下标含义:
i:取石子的开始下标【0,n)
j:取多少颗石子【1,n)
k:区间下标,在当前去j颗石子之内循环 ,k∈【0,j)
由k切割为了两个区间:【i,k】和【i+k+1,j-k-1】
含义为:区间①为从i开始,取了k颗石子。区间②为从区间①结束得地方(i+k+1)取剩下的石子(j-k-1)。
加起来一共取了j-1颗。
对于k的实例分析:
●j=1时(取1颗),k=0,
划分为两个区间:①【0,0】,②【1,0】,这两个区间的dp值都是0 。然后加上sum(i,i+j),得到了正确的值
●j=2时,k=0~1,
k=0时,①【0,0】,②【1,1】,用s=i,e=i+j还原:【0,0】,【1,2】
k=1时,①【0,1】,②【2,0】, ~ :【0,1】,【2,2】
注:以上分析都没有用“取模运算”来对下标进行重定位
理解后我的编码(主代码):
int i,j,k; I("%d",&n); F(i,0,n){ I("%d",&a[i]); } sum[0] =a[0]; F(i,1,n) sum[i]=a[i]+sum[i-1]; F(j,1,n){ //开始下标 F(i,0,n){ //取石子数目 int delta=getsum(i,i+j); //区间[i,i+j]的 和 mins[i][j]=MAX; //初始化mins F(k,0,j){ //区间下标 mins[i][j]=min(mins[i][j], mins[i][k] + mins[(i+k+1)%n][j-k-1]+delta) ; maxs[i][j]=max(maxs[i][j], maxs[i][k] + maxs[(i+k+1)%n][j-k-1]+delta) ; } } } //计算完毕,找出最后的最大值和最小值 F(i,0,n){ max_ans=max(max_ans,maxs[i][n-1]); min_ans=min(min_ans,mins[i][n-1]); } O("%d %d ",min_ans,max_ans);
完整代码:
#include <stdio.h> #include <memory.h> #include <math.h> #include <string> #include <vector> #include <set> #include <stack> #include <queue> #include <algorithm> #include <map> #define I scanf #define OL puts #define O printf #define F(a,b,c) for(a=b;a<c;a++) #define FF(a,b) for(a=0;a<b;a++) #define FG(a,b) for(a=b-1;a>=0;a--) #define LEN 300 #define MAX 1<<30 #define V vector<int> using namespace std; int a[LEN]; int sum[LEN]; int maxs[LEN][LEN]; int mins[LEN][LEN]; int max_ans=0; int min_ans=MAX; int n; int getsum(int i,int j){ if(j>=n){ return getsum(i,n-1) + getsum(0,j%n); }else{ return sum[j] - ( i>0 ? sum[i-1] : 0 ); } } int main(){ // freopen("D:/CbWorkspace/动态规划/石子合并.txt","r",stdin); int i,j,k; I("%d",&n); F(i,0,n){ I("%d",&a[i]); } sum[0] =a[0]; F(i,1,n) sum[i]=a[i]+sum[i-1]; F(j,1,n){ //开始下标 F(i,0,n){ //取石子数目 int delta=getsum(i,i+j); //区间[i,i+j]的 和 mins[i][j]=MAX; //初始化mins F(k,0,j){ //区间下标 mins[i][j]=min(mins[i][j], mins[i][k] + mins[(i+k+1)%n][j-k-1]+delta) ; maxs[i][j]=max(maxs[i][j], maxs[i][k] + maxs[(i+k+1)%n][j-k-1]+delta) ; } } } //计算完毕,找出最后的最大值和最小值 F(i,0,n){ max_ans=max(max_ans,maxs[i][n-1]); min_ans=min(min_ans,mins[i][n-1]); } O("%d %d ",min_ans,max_ans); return 0; }