区间dp
1.石子合并
题目描述
设有 N 堆石子排成一排,其编号为 1,2,3,…,N。
每堆石子有一定的质量,可以用一个整数来描述,现在要将这 N 堆石子合并成为一堆。
每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。
例如有 4 堆石子分别为 1 3 5 2, 我们可以先合并 1、2 堆,代价为 4,得到 4 5 2, 又合并 1,2 堆,代价为 9,得到 9 2 ,再合并得到 11,总代价为 4+9+11=24;
如果第二步是先合并 2,3 堆,则代价为 7,得到 4 7,最后一次合并代价为 11,总代价为 4+7+11=22。
问题是:找出一种合理的方法,使总的代价最小,输出最小代价。
输入格式
第一行一个数 N 表示石子的堆数 N。
第二行 N 个数,表示每堆石子的质量(均不超过 1000)。
输出格式
输出一个整数,表示最小代价。
数据范围
1≤N≤300
输入样例:
输出样例:
解题思路
代码实现
#include<iostream>
#include<cstring>
using namespace std;
const int N=310;
int s[N],f[N][N];
int main()
{
int n;
cin>>n;
memset(f,0x3f,sizeof(f));//记得初始化
for(int i=1;i<=n;i++){
cin>>s[i];
f[i][i]=0; //初始化
}
for(int i=1;i<=n;i++) s[i]+=s[i-1];
for(int len=2;len<=n;len++){//区间长度一共n堆石子,当n取一的时候就是一堆,不用合并
for(int l=1;l+len-1<=n;l++){//l~k,k~r-1//区间左端点
int r=l+len-1;//区间右端点
for(int k=l;k<r;k++){//k在区间左右端点之间
f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
}
}
}
cout<<f[1][n]<<endl;
return 0;
}
2.环形石子合并
题目描述
将 n 堆石子绕圆形操场排放,现要将石子有序地合并成一堆。
规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。
请编写一个程序,读入堆数 n 及每堆的石子数,并进行如下计算:
选择一种合并石子的方案,使得做 n−1 次合并得分总和最大。
选择一种合并石子的方案,使得做 n−1 次合并得分总和最小。
输入格式
第一行包含整数 n,表示共有 n 堆石子。
第二行包含 n 个整数,分别表示每堆石子的数量。
输出格式
输出共两行:
第一行为合并得分总和最小值,
第二行为合并得分总和最大值。
数据范围
1≤n≤200
输入样例:
输出样例:
解题思路
代码实现
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=410;
int f[N][N],s[N],w[N],g[N][N];//f用来求最小,g用来求最大
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>w[i];
w[i+n]=w[i];
}
memset(f,0x3f,sizeof f);
memset(g,-0x3f,sizeof g);
for(int i=1;i<=n*2;i++) s[i]=s[i-1]+w[i];
for(int len=1;len<=n;len++){//枚举长度是n
for(int i=1;i+len-1<=n*2;i++){//枚举区间是1~2n
int j=i+len-1;
if(len==1) f[i][j]=g[i][j]=0;
else{
for(int k=i;k<j;k++){
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]);
g[i][j]=max(g[i][j],g[i][k]+g[k+1][j]+s[j]-s[i-1]);
}
}
}
}
int minf=0x3f3f3f3f;
int maxf=-0x3f3f3f3f;
for(int i=1;i<=n;i++){
minf=min(f[i][i+n-1],minf);
maxf=max(maxf,g[i][i+n-1]);
}
cout<<minf<<endl<<maxf<<endl;
return 0;
}
3.能量项链
题目描述
在 Mars 星球上,每个 Mars 人都随身佩带着一串能量项链,在项链上有 N 颗能量珠。
能量珠是一颗有头标记与尾标记的珠子,这些标记对应着某个正整数。
并且,对于相邻的两颗珠子,前一颗珠子的尾标记一定等于后一颗珠子的头标记。
因为只有这样,通过吸盘(吸盘是 Mars 人吸收能量的一种器官)的作用,这两颗珠子才能聚合成一颗珠子,同时释放出可以被吸盘吸收的能量。
如果前一颗能量珠的头标记为 m,尾标记为 r,后一颗能量珠的头标记为 r,尾标记为 n,则聚合后释放的能量为 m×r×n(Mars 单位),新产生的珠子的头标记为 m,尾标记为 n。
需要时,Mars 人就用吸盘夹住相邻的两颗珠子,通过聚合得到能量,直到项链上只剩下一颗珠子为止。
显然,不同的聚合顺序得到的总能量是不同的,请你设计一个聚合顺序,使一串项链释放出的总能量最大。
例如:设 N=4,4 颗珠子的头标记与尾标记依次为 (2,3)(3,5)(5,10)(10,2)。
我们用记号 ⊕ 表示两颗珠子的聚合操作,(j⊕k) 表示第 j,k 两颗珠子聚合后所释放的能量。则
第 4、1 两颗珠子聚合后释放的能量为:(4⊕1)=10×2×3=60。
这一串项链可以得到最优值的一个聚合顺序所释放的总能量为 ((4⊕1)⊕2)⊕3)=10×2×3+10×3×5+10×5×10=710。
输入格式
输入的第一行是一个正整数 N,表示项链上珠子的个数。
第二行是 N 个用空格隔开的正整数,所有的数均不超过 1000,第 i 个数为第 i 颗珠子的头标记,当 i<N 时,第 i 颗珠子的尾标记应该等于第 i+1 颗珠子的头标记,第 N 颗珠子的尾标记应该等于第 1 颗珠子的头标记。
至于珠子的顺序,你可以这样确定:将项链放到桌面上,不要出现交叉,随意指定第一颗珠子,然后按顺时针方向确定其他珠子的顺序。
输出格式
输出只有一行,是一个正整数 E,为一个最优聚合顺序所释放的总能量。
数据范围
4≤N≤100,
1≤E≤2.1×109
输入样例:
输出样例:
解题思路
代码实现
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=220;
int f[N][N],w[N];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>w[i];
w[i+n]=w[i];
}
for(int len=3;len<=n+1;len++){//len从3开始
for(int i=1;i+len-1<=2*n;i++){
int j=i+len-1;
for(int k=i+1;k<j;k++){//k的取值[i+1,j-1]
f[i][j]=max(f[i][j],f[i][k]+f[k][j]+w[i]*w[k]*w[j]);
}
}
}
int res=0;
for(int i=1;i<=n;i++) res=max(res,f[i][i+n]);
cout<<res<<endl;
return 0;
}
4.凸多边形的划分
题目描述
给定一个具有 N 个顶点的凸多边形,将顶点从 1 至 N 标号,每个顶点的权值都是一个正整数。
将这个凸多边形划分成 N−2 个互不相交的三角形,对于每个三角形,其三个顶点的权值相乘都可得到一个权值乘积,试求所有三角形的顶点权值乘积之和至少为多少。
输入格式
第一行包含整数 N,表示顶点数量。
第二行包含 N 个整数,依次为顶点 1 至顶点 N 的权值。
输出格式
输出仅一行,为所有三角形的顶点权值乘积之和的最小值。
数据范围
N≤50,
数据保证所有顶点的权值都小于109
输入样例:
输出样例:
解题思路
代码实现
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
const int N=55,M=35,INF=1e9;
int w[N];
LL f[N][N][M];//M用来表示一个向量 将每一位都存下来
void add(LL a[],LL b[]){//高精度加
static LL c[M];
memset(c,0,sizeof c);
for(int i=0,t=0;i<M;i++){
t+=a[i]+b[i];
c[i]=t%10;
t/=10;
}
memcpy(a,c,sizeof(c));
}
void mull(LL a[],int b){//高精度乘
static LL c[M];
memset(c,0,sizeof c);
LL t=0;
for(int i=0;i<M;i++){
t+=a[i]*b;
c[i]=t%10;
t/=10;
}
memcpy(a,c,sizeof(c));
}
int cmp(LL a[],LL b[]){//比较两个高精度数的大小
for(int i=M-1;i>=0;i--){//注意,由于数是倒叙存放的 所以从后往前比较
if(a[i]>b[i]) return 1;
else if(a[i]<b[i]) return -1;
}
return 0;
}
void print(LL a[]){
int k=M-1;
while(k&&!a[k]) k--;
while(k>=0) cout<<a[k--];
cout<<endl;
}
int main()
{
int n;
cin>>n;
LL tmp[M];
for(int i=1;i<=n;i++) cin>>w[i];
for(int len=3;len<=n;len++){
for(int i=1;i+len-1<=n;i++){
int j=i+len-1;
f[i][j][M-1]=1;//初始化第M-1位是1,就相当于正无穷,因为数是倒叙放在数组里的
for(int k=i+1;k<j;k++)
{
memset(tmp,0,sizeof tmp);
tmp[0]=w[i];
mull(tmp,w[k]);
mull(tmp,w[j]);
add(tmp,f[i][k]);
add(tmp,f[k][j]);
if(cmp(f[i][j],tmp)>0){
memcpy(f[i][j],tmp,sizeof tmp);
}
}
}
}
print(f[1][n]);
return 0;
}
5.加分二叉树
题目描述
解题思路
代码实现
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=35;
int w[N];
int f[N][N],g[N][N];
void dfs(int l,int r){
if(l>r) return;
int k= g[l][r];
cout<<k<<' ';
dfs(l,k-1);
dfs(k+1,r);
}
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>w[i];
for(int len=1;len<=n;len++){
for(int i=1;i+len-1<=n;i++){
int j=i+len-1;
for(int k=i;k<=j;k++){
int left=k==i?1:f[i][k-1];//如果左子树为空 加分为1
int right=k==j?1:f[k+1][j];//如果右子树为空 加分为1
int score=left*right+w[k];
if(i==j) score=w[k];//如果是叶子结点 就是它本身的分数
if(score>f[i][j]){
f[i][j]=score;
g[i][j]=k;//记录当前的根结点
}
}
}
}
cout<<f[1][n]<<endl;
dfs(1,n);
cout<<endl;
return 0;
}
初学区间dp,有错误的话,欢迎大家指出,对于题解有什么好的意见也欢迎提出,加油呀~