经典区间DP

区间DP,就是在区间上做DP(谁都知道)

要搞一个区间,就需要一个双重循环:(写法一大堆,这是其中一种)

for (int len=2;len<=n;len++)//枚举区间长度len
 for (int i=1;i<=n;i++)//枚举区间左端点
  int j=i+len-1;//计算区间右端点 

然后balabalaDP就好了

石子合并:

在操场上沿一直线排列着n堆石子。现要将石子有次序地合并成一堆。

规定每次只能选相邻的两堆石子合并成新的一堆,并将新的一堆石子数计为该次合并的得分。 我们希望这n-1次合并后得到的得分总和最小。

这就模板题了,学区间DP必切的一道。

#include <bits/stdc++.h>
using namespace std;
int n;
int f[1000][1000];
int sum[1000];
inline int read()
{
	int c=getchar(),flag=1,sum=0;
	for (;c<'0' || c>'9';c=getchar())
	 if (c=='-') flag=-1;
	for (;c>='0' && c<='9';c=getchar())
	 sum=(sum<<3)+(sum<<1)+c-48;
	return sum*flag;
}
inline void init()
{
	n=read();
	for (int i=1;i<=n;i++)
	  {
	  	int x=read();
	  	sum[i]=sum[i-1]+x;//前缀和 
	  }
}
inline void work()
{
	for (int len=1;len<=n;len++)
	 for (int i=1,j=len+1;j<=n;i++,j++)
	  {
	  	f[i][j]=1000000007;//保证不会一直选择f[i][j]的决策
	  	for (int k=i;k<j;k++)
	  	 f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+sum[j]-sum[i-1]);//sum[j]-sum[i-1]是i-j的区间和
	  }
	printf("%d\n",f[1][n]);//最终结果合并到1-n的区间中
}
int main()
{
    init();
    work();
	return 0;
} 

矩阵连乘:

给定 n个矩阵{A1,A2,...,An},考察这 n个矩阵的连乘积 A1A2...An。由于矩阵乘法满足结合律,故计算矩阵的连乘积可以有许多不同的计算次序,这种计算次序可以用加括号的方式来确定。
矩阵连乘积的计算次序与其计算量有密切关系。例如,考察计算 3 个矩阵{A1,A2,A3}连乘积的例子。 设这3个矩阵的维数分别为101001005550 10∗100,100∗5,和5∗50

若按(A1A2)A3 计算,3 个矩阵连乘积需要的数乘次数为 101005+10550=7500 10∗100∗5+10∗5∗50=7500

若按 A1(A2A3)计算,则总共需要100550+1010050=75000 100∗5∗50+10∗100∗50=75000次数乘。

现在你的任务是给出一个矩阵连乘式,计算其需要的最少乘法次数。

这也是基本的套路,根据题意需要把求和改为矩阵乘法,另外还要注意将老维数套到新的矩阵中

#include <bits/stdc++.h>
using namespace std;
int n;
int f[1000][1000]={};
struct hazaking
{
	int h,l;
}a[1000][1000];
inline int read()
{
	int c=getchar(),flag=1,sum=0;
	for (;c<'0' || c>'9';c=getchar())
	 if (c=='-') flag=-1;
	for (;c>='0' && c<='9';c=getchar())
	 sum=(sum<<3)+(sum<<1)+c-48;
	return sum*flag;
}
inline void init()
{
	n=read();
	for (int i=1;i<=n;i++)
	  {
	  	a[i][i].h=read();
	  	a[i][i].l=read();
	  }
}
inline void work()
{
	for (int len=1;len<=n;len++)
	 for (int i=1,j=len+1;j<=n;i++,j++)
	  {
	  	int mi=100000007;
	   for (int k=i;k<j;k++)
	    if (f[i][k]+f[k+1][j]+a[i][k].h*a[i][k].l*a[k+1][j].l<mi)
	  	 {
	  	 	 	mi=f[i][k]+f[k+1][j]+a[i][k].h*a[i][k].l*a[k+1][j].l;//合并
	  	 	 	a[i][j].h=a[i][k].h;//新维数
	  	 	 	a[i][j].l=a[k+1][j].l;//新维数
	  	 }
	  	f[i][j]=mi;
	  }
	printf("%d\n",f[1][n]);//最终结果合并到1-n的区间中
}
int main()
{
    init();
    work();
	return 0;
}

能量项链:

在 Mars 星球上,每个 Mars 人都随身佩带着一串能量项链。在项链上有

N N 颗能量珠。能量珠是一颗有头标记与尾标记的珠子,这些标记对应着某个正整数。并且,对于相邻的两颗珠子,前一颗珠子的尾标记一定等于后一颗珠子的头标记。因为只有这样,通过吸盘(吸盘是 Mars Mars 人吸收能量的一种器官)的作用,这两颗珠子才能聚合成一颗珠子,同时释放出可以被吸盘吸收的能量。如果前一颗能量珠的头标记为 m m,尾标记为 r r,后一颗能量珠的头标记为 r r ,尾标记为 n n,则聚合后释放的能量为 m×r×n m×r×n(Mars 单位),新产生的珠子的头标记为 m m,尾标记为 n n

需要时,Mars 人就用吸盘夹住相邻的两颗珠子,通过聚合得到能量,直到项链上只剩下一颗珠子为止。显然,不同的聚合顺序得到的总能量是不同的,请你设计一个聚合顺序,使一串项链释放出的总能量最大。

例如:设 N=4 N=44 4 颗珠子的头标记与尾标记依次为 (2,3)(3,5)(5,10)(10,2) (2,3)(3,5)(5,10)(10,2)。我们用记号   表示两颗珠子的聚合操作,(jk) (j⊕k) 表示第 jk j,k 两颗珠子聚合后所释放的能量。则第 41 4、1 两颗珠子聚合后释放的能量为:

(41)=10×2×3=60 (4⊕1)=10×2×3=60

这一串项链可以得到最优值的一个聚合顺序所释放的总能量为

(((41)2)3)=10×2×3+10×3×5+10×5×10=710 (((4⊕1)⊕2)⊕3)=10×2×3+10×3×5+10×5×10=710

这也是基础套路题,比较老的NOIP的DP题,跟矩阵连乘差不多

但由于题目给出的是一条项链(一个环),需要破环成链

#include <bits/stdc++.h>
using namespace std;
int n;
int f[1000][1000];
int a[100000];
int ans=0;
inline int read()
{
	int c=getchar(),flag=1,sum=0;
	for (;c<'0' || c>'9';c=getchar())
	 if (c=='-') flag=-1;
	for (;c>='0' && c<='9';c=getchar())
	 sum=(sum<<3)+(sum<<1)+c-48;
	return sum*flag;
}
inline void init()
{
	n=read();
    for (int i=1;i<=n;i++)
     a[i]=read();
}
inline void work()
{
	for (int i=n+1;i<=2*n;i++)
	 a[i]=a[i-n];//破环成链
	a[0]=a[n];//破环成链
	for (int len=1;len<2*n;len++)
	 for (int i=len;i>=1 && i>len-n;i--)
	  {
	  	for (int k=i;k<len;k++)
	  	 {
	  	 	f[i][len]=max(f[i][len],f[i][k]+f[k+1][len]+a[i]*a[k+1]*a[len+1]);//合并
	  	 }
	  }
	for (int i=1;i<=n;i++)
	 ans=max(ans,f[i][n+i-1]);//得到答案
	printf("%d\n",ans);
}
int main()
{
    init();
    work();
	return 0;
}

四边形优化扔掉



猜你喜欢

转载自blog.csdn.net/fsl123fsl/article/details/80301007
今日推荐