2298 石子合并

2008年省队选拔赛山东

 时间限制: 1 s
 空间限制: 256000 KB
 题目等级 : 黄金 Gold
 查看运行结果
 
 
题目描述 Description

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

  试设计一个算法,计算出将N堆石子合并成一堆的最小得分。

输入描述 Input Description

  第一行是一个数N。

  以下N行每行一个数A,表示石子数目。

输出描述 Output Description

  共一个数,即N堆石子合并成一堆的最小得分。

样例输入 Sample Input

4

1

1

1

1

样例输出 Sample Output

8

数据范围及提示 Data Size & Hint

对于 30% 的数据,1≤N≤100

对于 60% 的数据,1≤N≤1000

对于 100% 的数据,1≤N≤40000

对于 100% 的数据,1≤A≤200

分类标签 Tags 点此展开

 

题意: 石子合并问题, 将相邻两堆石子合并, 每次得分是合并成新的一堆石子个数, 最后累加最小值.

解题思路:

1. 这类题目一开始想到是DP, 设dp[i][j]表示第i堆石子到第j堆石子合并最小得分.

状态方程: dp[i][j] = min(dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);

sum[i]表示第1到第i堆石子总和. 递归记忆化搜索即可.

2. 不过此题有些不一样, 1<=n<=50000范围特大, dp[50000][50000]开不到这么大数组.

问题分析:

(1). 假设我们只对3堆石子a,b,c进行比较, 先合并哪2堆, 使得得分最小.

score1 = (a+b) + ( (a+b)+c )

score2 = (b+c) + ( (b+c)+a )

再次加上score1 <= score2, 化简得: a <= c, 可以得出只要a和c的关系确定,

合并的顺序也确定.

(2). GarsiaWachs算法, 就是基于(1)的结论实现.找出序列中满足stone[i-1] <=

stone[i+1]最小的i, 合并temp = stone[i]+stone[i-1], 接着往前面找是否

有满足stone[j] > temp, 把temp值插入stone[j]的后面(数组的右边). 循环

这个过程一直到只剩下一堆石子结束.

(3). 为什么要将temp插入stone[j]的后面, 可以理解为(1)的情况

从stone[j+1]到stone[i-2]看成一个整体 stone[mid],现在stone[j],

stone[mid], temp(stone[i-1]+stone[i-1]), 情况因为temp < stone[j],

因此不管怎样都是stone[mid]和temp先合并, 所以讲temp值插入stone[j]

的后面是不影响结果.

#include<cstdio>
#include<iostream>
#define ll long long
using namespace std;
#define N 50010
int t=,n,a[N];
ll ans=;
void work(int k){
int tmp=a[k-]+a[k];
ans+=tmp;
for(int i=k;i<t-;i++)
a[i]=a[i+];
t--;
int j=;
for(j=k-;j>&&a[j-]<tmp;j--)
a[j]=a[j-];
a[j]=tmp;
while(j>=&&a[j]>=a[j-]){
int d=t-j;
work(j-);
j=t-d;
}
}
int main(){
scanf("%d",&n);
for(int i=;i<n;i++)
scanf("%d",&a[i]);
for(int i=;i<n;i++){
a[t++]=a[i];
while(t>=&&a[t-]<=a[t-])
work(t-);
}
while(t>)
work(t-);
printf("%lld\n",ans);
return ;
}

-----------------------------------------------------------------------------------------------------------------------------------------------

华丽的分割线

------------------------------------------------------------------------------------------------------------------------------------------------

以下是石子合并系列问题的常用思路,现附上。(以下大部分来自网络,代码未经测试,看看思路就好)

石子合并问题是最经典的DP问题。首先它有如下3种题型:

(1)有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动任意的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(或最大)。

分析:当然这种情况是最简单的情况,合并的是任意两堆,直接贪心即可,每次选择最小的两堆合并。本问题实际上就是哈夫曼的变形。

(2)有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动相邻的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(或最大)。

分析:我们熟悉矩阵连乘,知道矩阵连乘也是每次合并相邻的两个矩阵,那么石子合并可以用矩阵连乘的方式来解决。
设dp[i][j]表示第i到第j堆石子合并的最优值,sum[i][j]表示第i到第j堆石子的总数量。那么就有状态转移公式:
 

直线取石子问题的平行四边形优化:

#include <iostream>
#include <string.h>
#include <stdio.h> using namespace std;
const int INF = << ;
const int N = ; int dp[N][N];
int p[N][N];
int sum[N];
int n; int getMinval()
{
for(int i=; i<=n; i++)
{
dp[i][i] = ;
p[i][i] = i;
}
for(int len=; len<n; len++)
{
for(int i=; i+len<=n; i++)
{
int end = i+len;
int tmp = INF;
int k = ;
for(int j=p[i][end-]; j<=p[i+][end]; j++)
{
if(dp[i][j] + dp[j+][end] + sum[end] - sum[i-] < tmp)
{
tmp = dp[i][j] + dp[j+][end] + sum[end] - sum[i-];
k = j;
}
}
dp[i][end] = tmp;
p[i][end] = k;
}
}
return dp[][n];
} int main()
{
while(scanf("%d",&n)!=EOF)
{
sum[] = ;
for(int i=; i<=n; i++)
{
int val;
scanf("%d",&val);
sum[i] = sum[i-] + val;
}
printf("%d\n",getMinval());
}
return ;
}
 
(3)问题(2)的是在石子排列是直线情况下的解法,如果把石子改为环形排列,又怎么做呢?
分析:状态转移方程为:
其中有:
#include <iostream>
#include <string.h>
#include <stdio.h> using namespace std;
const int INF = << ;
const int N = ; int mins[N][N];
int maxs[N][N];
int sum[N],a[N];
int minval,maxval;
int n; int getsum(int i,int j)
{
if(i+j >= n) return getsum(i,n-i-) + getsum(,(i+j)%n);
else return sum[i+j] - (i> ? sum[i-]:);
} void Work(int a[],int n)
{
for(int i=;i<n;i++)
mins[i][] = maxs[i][] = ;
for(int j=;j<n;j++)
{
for(int i=;i<n;i++)
{
mins[i][j] = INF;
maxs[i][j] = ;
for(int k=;k<j;k++)
{
mins[i][j] = min(mins[i][j],mins[i][k] + mins[(i+k+)%n][j-k-] + getsum(i,j));
maxs[i][j] = max(maxs[i][j],maxs[i][k] + maxs[(i+k+)%n][j-k-] + getsum(i,j));
}
}
}
minval = mins[][n-];
maxval = maxs[][n-];
for(int i=;i<n;i++)
{
minval = min(minval,mins[i][n-]);
maxval = max(maxval,maxs[i][n-]);
}
} int main()
{
while(scanf("%d",&n)!=EOF)
{
for(int i=;i<n;i++)
scanf("%d",&a[i]);
sum[] = a[];
for(int i=;i<n;i++)
sum[i] = sum[i-] + a[i];
Work(a,n);
printf("%d %d\n",minval,maxval);
}
return ;
}
可以看出,上面的(2)(3)问题的时间复杂度都是O(n^3),由于过程满足平行四边形法则,故可以进一步优化到O(n^2)。

最新文章

  1. phylogeny analysis
  2. JavaScript表单处理(上)
  3. JavaWeb学习总结(七)—HttpServletRequest
  4. 【iOS开发】添加子控件方式(懒加载,GCC)
  5. 结对作业1----基于flask框架的四则运算生成器
  6. Delphi的RzDbgrid改变某行的背景色
  7. Python【第三课】 函数基础
  8. 安装linux
  9. new
  10. docker部署Javaweb环境数据库连接问题
  11. Quartz.Net进阶之四:CronTrigger 详述
  12. day6 六、元组、字典、集合的基本操作和内置方法
  13. Values &amp; Reference:值和引用
  14. odoo明细表汇总数据
  15. Linux下烧写工具DNW和USB驱动安装(一)
  16. 使用redis镜像
  17. 不到 200 行代码,教你如何用 Keras 搭建生成对抗网络(GAN)【转】
  18. java通过java.net.URL发送http请求调用接口
  19. Keepalived stable tarball
  20. chest

热门文章

  1. Xamarin.Forms 调用腾讯地图
  2. C#中 protected internal 和 internal 的区别
  3. 前端存储之indexedDB
  4. iOS开发--Mac下server搭建
  5. node - 上传文件并且修改名称
  6. node - post - 上传图片
  7. Oracle 数据库监听配置
  8. Laravel之事件
  9. java编程思想读书笔记 第十二章 通过异常处理错误(下)
  10. 原生JS实现的h5小游戏-植物大战僵尸