是最讨厌的DP啊/哭/,但是总考不是/大哭/,我有什么办法/哭死了/。
好吧,DP还好几种:区间、树形、数位..@$#%4*(我才没有骂人)切腹自尽。
其实区间DP的用法还是比较单一的?(至少下面的题是?)我还不是一样做不出来。
目录
#10147. 「一本通 5.1 例 1」石子合并
#10148. 「一本通 5.1 例 2」能量项链
#10149. 「一本通 5.1 例 3」凸多边形的划分
#10150. 「一本通 5.1 练习 1」括号配对
#10151. 「一本通 5.1 练习 2」分离与合体
#10152. 「一本通 5.1 练习 3」矩阵取数游戏
题目
将n堆石子绕圆形操场排放,要将石子有序地合并成一堆。
每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。
选择一种合并石子的方案,使得做n-1次合并得分总和最大和最小。
对于100%的数据,有1<=n<=200。
once upon a time,师兄好像讲过这道题和能量项链。
那时候我太弱了啥都不会一脸懵逼,傻逼ba我。
直接放师兄留下的PPT上的东西ba(讲的真的超级清楚了)
这个里面不是环形的,请先理解一下更方便后面环形的操作(大同小异)
定义
- 定义sum[i]表示从第1堆石子到第i堆石子的石子数目和。
- 定义f[i][j]表示将从第i堆到第j堆石子合并成一堆后的最大分数值。
状态转移方程
- 很明显我们可以得出下面这个结论:
- 第i堆到第j堆合并后的最大分值=第i堆到第k堆合并到的最大分值+第k+1堆到第j堆合并到的最大分值+第i堆到第j堆的石子数总和。
- K是i到j中的某个数。
- K是哪个数不重要,重要的是这个k能使合并完第i堆到第j堆的分数值最大。
- 于是得出方程:f[i][j]=f[i][k]+f[k+1][j]+sum[j]-sum[i-1]
你需要的环形,i have an idea。
两个小小的不一样:
简单的double。
举个栗子:三堆石子,石子数目分别为1,2,3
double之后:1,2,3,1,2,3
这样就ok啦(分别是红色的,下划线的,加粗的)
你想要哪种就哪种。这样就可以处理环形的了。
你需要找的就是最大的f[i][i+石堆数量-1]。
对了这道题还有点不一样就是既要求最大也要求最小。
当然都是一样的道理。
#include
#include
#include
using namespace std;
int n,minn=1e9,maxx=0;
int a[410],sum[410][410];
int fmin[410][410],fmax[410][410];
//fmin记录最小,fmax记录最大
int main()
{
scanf("%d",&n);
memset(fmin,63,sizeof(fmin));
memset(fmax,0,sizeof(fmax));
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
a[i+n]=a[i];
}
for(int i=1;i<=2*n;i++)
{
sum[i][i]=a[i];
fmin[i][i]=fmax[i][i]=0;
for(int j=i+1;j<=2*n;j++)
{
sum[i][j]=sum[i][j-1]+a[j];
}
}
for(int c=2;c<=n;c++)//枚举长度
{
for(int i=1;i<=2*n-c+1;i++)//开头
{
int j=i+c-1;//结尾
for(int k=i;k<=j-1;k++)
{
fmin[i][j]=min(fmin[i][j],fmin[i][k]+fmin[k+1][j]+sum[i][j]);
fmax[i][j]=max(fmax[i][j],fmax[i][k]+fmax[k+1][j]+sum[i][j]);
if(c==n)
{
minn=min(minn,fmin[i][j]);
maxx=max(maxx,fmax[i][j]);
}
}
}
}
printf("%d\n%d",minn,maxx);
return 0;
}
题目
在项链上有n颗能量珠。
能量珠有头标记和尾标记,并且对于相邻的两颗珠子,前一颗珠子的尾标记等于后一颗珠子的头标记。
如果一颗能量珠头标记为m,尾标记为r,后一颗能量珠头标记为r,尾标记为n,则聚合后释放出m*r*n的能量。
新珠子头标记为m,尾标记为n。
一串项链上有n颗珠子,相邻两颗珠子可以合并成一个,设计一个聚合顺序使聚合后释放出的能量最大。
对于100%的数据,4<=n<=100。
还是那个PPT
这题和合并石子是非常类似的。
唯一的区别在它是环形。
那么怎么办呢?
因为它是环形的,所以我们可以选择从某两个能量珠中把这个环形的项链断开,成为一条直线。
当然地,我们也要知道从哪个断口切开最后得到的分数最大啊?
方法还是枚举。
枚举从每两个相邻珠子之间切开,看看从哪里切开最后得到的分值最大。
怎么跟上一题一样a
代码
#include
#include
#include
using namespace std;
int n,maxx=0;
int x[210],sum[210],f[210][210];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&x[i]); x[i+n]=x[i];
sum[i-1]=x[i]; sum[i-1+n]=sum[i-1];
}
sum[2*n]=sum[n];
for(int c=2;c<=n;c++)
{
for(int i=1;i<=2*n-c+1;i++)
{
int j=i+c-1;
for(int k=i;k<=j-1;k++)
{
f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+x[i]*sum[k]*sum[j]);
if(c==n) maxx=max(maxx,f[i][j]);
}
}
}
printf("%d",maxx);
return 0;
}
题目
给定一个具有N个顶点的凸多边形,每个顶点的权值都是一个正整数。
将这个凸多边形划分成N-2个互不相交的三角形,求这些三角形顶点的权值乘积和至少为多少。
对于100%的数据,有N<=50,每个点权值小于10^9。
为什么跟上面的不一样???你需要来个大拐弯!
画个图/滑稽/。
安利:https://blog.csdn.net/xuechen_gemgirl/article/details/80830106 图画的超级好看/赞/%%%
#include
#include
#include
using namespace std;
int n;
long long a[60],f[60][60];
int main()
{
scanf("%d",&n);
memset(f,63,sizeof(f));
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
f[i][i+1]=0;//相邻两个点组不成三角形
}
for(int c=2;c
然后你需要加高精/害怕/,淦!
#include
#include
#include
using namespace std;
int n;
struct node{int l=0,a[1300];node(){memset(a,0,sizeof(a));}}a[60],b,f[60][60];
node cf(node x,node y)//高精度乘法
{
node z; z.l=x.l+y.l-1;
for(int i=1;i<=x.l;i++)
{
for(int j=1;j<=y.l;j++)
{
z.a[i+j-1]+=x.a[i]*y.a[j];
}
}
for(int i=1;i<=z.l;i++)
{
if(z.a[i]>9)
{
z.a[i+1]+=z.a[i]/10;
z.a[i]%=10;
if(i+1>z.l) z.l++;
}
}
while(z.l>1&&z.a[z.l]==0) z.l--;
return z;
}
node jf(node x,node y)//高精度加法
{
node z; z.l=max(x.l,y.l);
if(x.l==0) return y;
else if(y.l==0) return x;
for(int i=1;i<=z.l;i++)
{
z.a[i]=x.a[i]+y.a[i]+z.a[i];
if(z.a[i]>9)
{
z.a[i+1]++; z.a[i]%=10;
if(i+1>z.l) z.l++;
}
}
while(z.l>1&&z.a[z.l]==0) z.l--;
return z;
}
int main()
{
scanf("%d",&n);
memset(f,63,sizeof(f));
for(int i=1;i<=n;i++)
{
long long x; scanf("%lld",&x);
while(x>0)
{
int xx=x%10; x/=10;
a[i].l++; a[i].a[a[i].l]=xx;
}
f[i][i+1].l=0;
}
for(int c=2;c1e6) continue;
if(f[k][j].l>1e6) continue;
b=jf(b,f[i][k]); b=jf(b,f[k][j]);
if(b.l=1;q--)
{
if(b.a[q]f[i][j].a[q]) break;
}
}
}
}
}
for(int i=f[1][n].l;i>=1;i--) printf("%d",f[1][n].a[i]);
return 0;
}
题目
GBE的定义:
1、空表达式是GBE
2、如果表达式A是GBE,则[A]与(A)都是 GBE
3、如果A与B都是GBE,那么AB是GBE
给出一个BE,求至少添加多少字符能使这个BE成为GBE。
对于100%的数据,输入的字符串长度小于100。
你只需要找配不上的,相应的你就知道你需要添加的啦。
一些解释放代码里了
#include
#include
#include
using namespace std;
char s[110];
int f[110][110];
int main()
{
scanf("%s",s+1);
int l=strlen(s+1);
for(int i=1;i<=l;i++)
{
f[i][i]=1;
f[i][i-1]=0;
}
for(int c=2;c<=l;c++)
{
for(int i=1;i<=l-c+1;i++)//开头
{
int j=i+c-1;//结尾
f[i][j]=0x3f3f3f3f;
for(int k=i;k<=j;k++)
{
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]);
//不需要担心k和k+1配上了却没记上
//因为总会找到f[i][k+1]和f[k+1][j]的
}
if((s[i]=='('&&s[j]==')')||(s[i]=='['&&s[j]==']'))//头和尾配上了
{
f[i][j]=min(f[i][j],f[i+1][j-1]);//第16行派上用处了
}
}
}
printf("%d",f[1][l]);//配不上的=需要添加的
return 0;
}
题目
n个区域里都放着一把金钥匙,每一把都有一定的价值。
可以选择1...n-1中的任何一个区域进入。
进入后会在k区域发生分离,并在各自区间内任选除区间末尾之外的任意一个区域再次发生分离……
重复以上所叙述的分离。
合体会获得(合并后所在区间左右端区域里金钥匙价值之和)*(之前分离的时候所在区域的金钥匙价值)。
求出最终可以获得的最大总价值,并按照分离阶段从前到后,区域从左到右的顺序,输出发生分离区域编号。
对于20%的数据,n<=10;
对于40%的数据,n<=50;
对于100%的数据,n,ai<=300保证运算过程和结果不超过32位正整数范围。
(合并后所在区间左右端区域里金钥匙价值之和)*(之前分离的时候所在区域的金钥匙价值)。
只要看这一句就好了。跟前面一样。我就说比较单一吧。
但是!!要输出顺序!!有人告诉我要暴力找?
吃水不忘挖井人。你得知道你从哪来,你才知道你要回哪去对吧。
比如:
你知道第一层的最优是在a处分离,
那么你可以分别去f[1][a]和f[a+1][n]找第二层你是从哪儿来的对吧,
然后这个在前面你找max的时候顺手记录一下就ok了对吧。
所以水到渠成,看代码。
#include
#include
#include
using namespace std;
long long f[310][310];
int n,a[310],s[310][310],an[310][310],aa[310];
void solve(int l,int r,int c)//区间[l,r],c表示分了几次
{
if(!s[l][r]||l>=r) return ;//s没有标注说明l>=r
aa[c]++; an[c][aa[c]]=s[l][r];//记录第c层分了些什么
//然后找我的下一层分的是什么
solve(l,s[l][r],c+1);
solve(s[l][r]+1,r,c+1);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int c=2;c<=n;c++)
{
for(int i=1;i<=n-c+1;i++)
{
int j=i+c-1;
for(int k=i;k<=j-1;k++)
{
long long t=f[i][j];
f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+(a[i]+a[j])*a[k]);
if(t!=f[i][j]) s[i][j]=k;//记录你从哪里来!(我从这里来/滑稽/)
}
}
}
printf("%lld\n",f[1][n]);
//跟前面的一模一样
solve(1,n,0);
for(int i=0;i
题目
n*m的矩阵,矩阵中每个元素均为非负整数。
每次取数时必须从每行各取走一个元素,共n个,m次取完所有元素。
每次取走的各个元素只能是所在行的行首或行尾。
每次取数都有一个的分值,为每行取数得分之和。求出取数后的最大得分。
每行取数得分=被取走元素值*2^i,其中i表示第i次取数,从i开始计数。
对于60%的数据,1<=n,m<=30,答案不超过10^16;
对于100%的数据,1<=n,m<=80,0<=ai,aj<=1000。
一头雾水?来个贪心。
肯定哪个小先取哪个啊,然后剩下打的跟2的更大次方乘。我又不傻。
高兴的敲。然后你(不才不是我我这么机智)发现你错了。
举个栗子:
1 5 1 10 3 3
我你的贪心顺序:1 3 3 5 1 10
=1*2+3*4+3*8+5*16+1*32+10*64 =2+12+24+80+32+640 =790
最佳的顺序:1 5 1 3 3 10
=1*2+5*4+1*8+3*16+3*32+10*64 =2+20+8+48+96+640 =814
然后你会发现这种栗子一抓一大把。
认命DP...不会从外面往里面搞是什么操作?好像(的确)不行。
那从里面随便挑一个往外D呗。然后核心我讲完了。
还是很单一。还是不会。还不懂就瞧瞧代码。
你猜猜2^80有多大??哈哈哈(按计算机)不吓你了。要用高精啊,好复杂。
先放一个思路简明的没有高精的。
#include
#include
#include
using namespace std;
int n,m,a[80][80];
long long k,kk=1,f[90][90],ans=0;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
scanf("%d",&a[i][j]);
if(i==1) kk*=2;
}
}
for(int q=1;q<=n;q++)//第q行
{
memset(f,0,sizeof(f)); k=kk;
for(int p=1;p<=m;p++) f[p][p]=a[q][p]*k;//最后一步的单个盈利
for(int c=2;c<=m;c++)
{
k/=2;
for(int i=1;i<=m-c+1;i++)//头
{
int j=i+c-1;//尾
f[i][j]=max(f[i][j],f[i+1][j]+a[q][i]*k);//你的上一个要么是左边
f[i][j]=max(f[i][j],f[i][j-1]+a[q][j]*k);// 要么是右边
}
}
ans+=f[1][m];//这一行的最大得分
}
printf("%lld",ans);
return 0;
}
高精来辣!
#include
#include
#include
using namespace std;
int n,m;
struct node{int l=0,a[110];node(){memset(a,0,sizeof(a));}}a[90][90],kk,c,k,f[90][90],aa,ans;
node cf(node x,node y)//高精度乘法
{
node z; z.l=x.l+y.l-1;
for(int i=1;i<=x.l;i++)
{
for(int j=1;j<=y.l;j++)
{
z.a[i+j-1]+=x.a[i]*y.a[j];
}
}
for(int i=1;i<=z.l;i++)
{
if(z.a[i]>9)
{
z.a[i+1]+=z.a[i]/10;
z.a[i]%=10;
if(i+1>z.l) z.l++;
}
}
while(z.l>1&&z.a[z.l]==0) z.l--;
return z;
}
node jf(node x,node y)//高精度加法
{
node z; z.l=max(x.l,y.l);
for(int i=1;i<=z.l;i++)
{
z.a[i]=x.a[i]+y.a[i]+z.a[i];
if(z.a[i]>9)
{
z.a[i+1]++; z.a[i]%=10;
if(i+1>z.l) z.l++;
}
}
while(z.l>1&&z.a[z.l]==0) z.l--;
return z;
}
node chu(node x)//高精度除法(只限除以2)
{
node z; z.l=x.l;
for(int i=x.l;i>=1;i--)
{
if(x.a[i]>0)
{
if(x.a[i]==1) x.a[i-1]+=10;
else
{
z.a[i]=x.a[i]/2;
if(x.a[i]%2) x.a[i-1]+=10;
}
}
}
while(z.l>1&&z.a[z.l]==0) z.l--;
return z;
}
int main()
{
scanf("%d%d",&n,&m);
c.l=kk.l=1; kk.a[1]=1; c.a[1]=2;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
int x; scanf("%d",&x);
while(x>0)
{
int xx=x%10; x/=10;
a[i][j].l++; a[i][j].a[a[i][j].l]=xx;
}
if(i==1) kk=cf(kk,c);
}
}
for(int q=1;q<=n;q++)
{
memset(f,0,sizeof(f)); k=kk;
for(int p=1;p<=m;p++) f[p][p]=cf(a[q][p],k);
for(int c=2;c<=m;c++)
{
k=chu(k);
for(int i=1;i<=m-c+1;i++)
{
int j=i+c-1;
aa=cf(a[q][i],k); aa=jf(aa,f[i+1][j]);
if(aa.l>f[i][j].l) f[i][j]=aa;
else if(aa.l==f[i][j].l)
{
for(int q=aa.l;q>=1;q--)
{
if(aa.a[q]f[i][j].a[q]) break;
}
}
aa=cf(a[q][j],k); aa=jf(aa,f[i][j-1]);
if(aa.l>f[i][j].l) f[i][j]=aa;
else if(aa.l==f[i][j].l)
{
for(int q=aa.l;q>=1;q--)
{
if(aa.a[q]>f[i][j].a[q])
{
f[i][j]=aa;
break;
}
else if(aa.a[q]=1;i--) printf("%d",ans.a[i]);
return 0;
}