来,先上线一道题。
求解区间dp的一般套路:
1,注意范围是否合理,例如会不会越界。
2,一般是从大范围往内推直到能简单处理的小范围(小区间)。
3,剪枝,一般初始化dp,要不是初始化的值,一般就直接返回了,这也类似于记忆化搜索。
4,注意特殊情况
题意:给出n组的key,value值,相邻的两个key如果它们的最大公约数不是1,那么我们可以将它们加起来,并清除。
问:找出最大的结果,可以挑选任意相邻的。
思路:
这里的解题思路参考此博客:https://blog.youkuaiyun.com/spark__s/article/details/52585459
dp题,用dp[[i][j]表示从i到j之间的最优解,转移方程可以这样写
当i>=j的时,dp[i][j]=0; (范围越界)
当i+1==j且key[i]和key[j]互质时,dp[i][j]=0; (缩小至能够简单处理的区间)
当i+1==j且key[i]和key[j]不互质时,dp[i][j]=val[i]+val[j]
其他情况为,枚举k从i到j-1, dp[i][j]=max(dp[i][k]+dp[k+1][j]).
上面还遗漏了一种情况,就是i+1到j-1之间都可以取,且i和j不互质,这种情况i到j所有的数都可以取。
解释下,为什么会遗漏这种情况,是说(i+1)到(j-1)满足条件,并移走之后,头跟尾会变成相邻的一对,喔,这里就有点坑了,考得真细。
以上就是所有情况,接下来写代码就可以了。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long LL;
LL key[305];
LL val[305];
LL dp[305][305];
LL gcd(LL a,LL b)
{
return b?gcd(b,a%b):a;
}
LL cal(int x,int y)
{
if(dp[x][y]!=-1) ///剪枝,不为-1,表示当前范围已经把最大结果计算好了,故直接返回就好了
return dp[x][y];
LL t=gcd(key[x],key[y]);
if(x>=y) ///范围越界
{
return dp[x][y]=0;
}
if(x+1==y&&t!=1) ///能简单处理的情况(小区间)
{
return dp[x][y]=val[x]+val[y];
}
if(x+1==y&&t==1) ///也是能简单处理的情况(小区间)
{
return dp[x][y]=0;
}
LL _max=0;
if(t!=1) ///特殊情况
{
LL xx=0;
for(int i=x+1;i<=y-1;i++)
{
xx+=val[i];
}
if(cal(x+1,y-1)==xx)
_max=xx+val[x]+val[y];
}
for(int i=x;i<=y-1;i++) ///从大范围开始往内缩
{///为什么i不是从x+1开始的呢?举个例子:key 2 4 8
///要是从x+1开始,那么就不会计算到[2,3],显然是不行的
_max=max(_max,cal(x,i)+cal(i+1,y));
}
return dp[x][y]=_max;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
memset(dp,-1,sizeof(dp));
int n;
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%lld",&key[i]);
}
for(int i=0;i<n;i++)
{
scanf("%lld",&val[i]);
}
LL ans=cal(0,n-1);
printf("%lld\n",ans);
}
}
我的标签:做个有情怀的程序员。