洛谷-P1880 石子合并(环形)

题目描述

在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分.

输入输出格式

输入格式:

数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.

输出格式:

输出共2行,第1行为最小得分,第2行为最大得分.

输入输出样例

输入样例#1: 

4
4 5 9 4

输出样例#1: 

43
54

这道题目...开始本来都已经想到了利用一个区间变量然后从间隔为1一直计算到间隔为n-1...结果还是没有做出来...

原因是 状态转移方程没写好...我最初的写法是dp[i][j] = max(dp[i][j-1]+sz[j],dp[i+1][j]+sum[i])...真是失败...唉....

 

这道题目很容易就能想到以间隔从小到大一层一层的来计算, 但是我没有抓好状态变化导致状态转移方程错误

因为在间隔为k的时候有很多中情况,我可以挑选一个间隔为1另一个间隔为k-1 也可以挑选间隔为2另一个间隔为k-2, 所以这些都要遍历到, 所以在确定了区间i到j的情况下, 我们还需要从中找出和最大或者最小的那个区间,所以额外再用一个循环来遍历.

因为是环形数组, 但是因为最大间隔为n-1,  实际并不能遍历到n*2以外的地方, 所以可以开一个两倍大的数组,用直线模拟环

另一个点就是sum数组, 它是一个前缀和数组,  sum[i]表示从1到i的元素的和, 这样要求区间i - j的和可以表示为sum[j] - sum[i-1].

最后一个点就是在递推遍历的时候i要遍历到2*n.

我之前用的是i<=n然后一直错, 到后面顶点查错的时候才发现, 在区间4-6区域, dp[4][6] = dp[4][4] + dp[5][6]+ sum[6]-sum[3].

其中因为点只遍历到n, 从而dp[5][6]的值并没有计算出来,所以导致结果错误.

妙啊

另外还是

I HATE DP

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
int sum[105];//前缀和
int sz[205];
int mindp[205][205],maxdp[205][205];
int n;
int main(){
    int tmp;
    cin>>n;
    memset(mindp,0,sizeof(mindp));
    memset(maxdp,0,sizeof(maxdp));
    sum[0] = 0;
    for(int i=1;i<=n;i++){
        cin>>tmp;
        sz[i] = sz[n+i] = tmp;
    }
    for(int i=1;i<=n*2;i++){
        sum[i] = sum[i-1]+sz[i];//sum是没有错误的 
    }
    
    for(int j=1;j<n;j++){
        for(int i=1;i<=2*n;i++){
            int tp = i+j;
            mindp[i][tp]=0x3f3f3f3f;
            for(int k=i;k<tp&&k<=2*n;k++){
                maxdp[i][tp]=max(maxdp[i][tp],maxdp[i][k]+maxdp[k+1][tp]+sum[tp]-sum[i-1]);
                mindp[i][tp]=min(mindp[i][tp],mindp[i][k]+mindp[k+1][tp]+sum[tp]-sum[i-1]);
//				if(i == 4 && tp == 6){
//					cout<<"k: "<<mindp[i][k]<<" "<<mindp[k+1][tp]<<endl;
//					cout<<sum[tp]<<" "<<sum[i-1]<<" "<<mindp[i][tp]<<endl;
//					cout<<"********"<<endl;
//				}
    //错误原因  在计算4 - 6 这个区间内的结果时会遇到dp[4][4] + dp[5][6] 然而此时dp[5][6]并没有赋值 
            }
        }
    }
    //测试 
//	for(int j=1;j<n;j++){
//		cout<<"j="<<j<<" : ";
//		for(int i=1;i<=n;i++){
//			int tp = i+j;
//			cout<<mindp[i][tp]<<" ";
//		}
//		cout<<endl;
//	}
    //
    int mi=0x3f3f3f3f,ma=0;
    for(int i=1;i<=n;i++){
//		cout<<mindp[i][i+n-1]<<" ";
        mi = min(mi,mindp[i][i+n-1]);
        ma = max(ma,maxdp[i][i+n-1]);
    }
//	cout<<endl;
//	for(int i=1;i<=n;i++){
//		cout<<maxdp[i][i+n-1]<<" ";
//	}
//	cout<<endl;
    cout<<mi<<"\n"<<ma<<endl;
    return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值