区间dp
区间dp
先在小区间上进行dp得到最优解,然后再合并小区间的最优解求得大区间的最优解,解题时,先解决小区间的问题,再将小区间合并为大区间,合并操作一般是将两个相邻区间合并
注:合并顺序从小区间到大区间,因该先从小到大枚举区间的长度,递推出j在哪里
一本通题解
T3:
2025.1.23补档:
我之前一直没有写这篇题解,因为我觉得我对这道题的理解一直不够深
再回过头来看终于悟透了
首先有一个贪心,就是原来就连续的木块一定一起删去,所以先把一段连续的木块看成一个,并记录一下颜色,及这一段的个数
突破口:根据经典套路,我们要用小区间合并成大区间,但是这里就有点复杂,于是我们用大区间分解成小区间,实现时采用记忆化搜索
对于一个区间
以右端点为突破口,每次只删除右端点
右端点有什么删除方式呢?
- 自己直接删除
- 和其右边的相同色的块连着删除,此时要满足这几个相同色块中间的不同色块只能独立消除
- 和右边的相同色块再连上其左边的相同色块一起删除
第一种是较好处理的,第二种就很难办了
怎么办?再加一维dp状态
第三种情况的话我们采用递推的方式,枚举区间
代码(建议结合代码理解):
#include<bits/stdc++.h>
using namespace std;
const int N=205;
int n,t,m,cnt;
int dp[N][N][N],col[N],num[N],a[N];
int dfs(int l,int r,int k){
if(dp[l][r][k]) return dp[l][r][k];
if(l==r) return (num[r]+k)*(num[r]+k);
dp[l][r][k]=dfs(l,r-1,0)+(num[r]+k)*(num[r]+k);
for(int i=l;i<r-1;i++){
if(col[i]==col[r]){
dp[l][r][k]=max(dp[l][r][k],dfs(l,i,num[r]+k)+dfs(i+1,r-1,0));
}
}
return dp[l][r][k];
}
int main(){
scanf("%d",&t);
while(t--){
cnt++;
n=0;
memset(dp,0,sizeof(dp));
scanf("%d",&m);
for(int i=1;i<=m;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=m;i++){
if(a[i-1]==a[i]){
num[n]++;
}
else{
col[++n]=a[i];
num[n]=1;
}
}
// for(int i=1;i<=n;i++){
// printf("%d %d\n",col[i],num[i]);
// }
printf("Case %d: %d\n",cnt,dfs(1,n,0));
}
}
T4:
啊啊啊为什么区间dp的题想根本想不出来啊啊啊
设
我们分情况讨论
考虑如果
如何判断
只要满足条件
因为正常的合并条件为
如果
我们自然是将大区间拆分小区间的方法,就是正常区间dp做法,枚举断点k即可
T5:
推式子。。。
求最小值,经典操作,先把固定的东西丢掉
去掉根号
考虑用二维区间dp实现
这里有一点变式,因为这个不是从小区间推导到大区间,而是从前一次的状态推导到这一次,并不需要区间从小到大枚举,但是我们是倒推,从一个小矩形往边上加块,推得一个大区间,所以我们要把那些不合法还没有通过推导得到的状态设为inf
转移
这个是横着割竖着割不同的情况,枚举g,我们可以分别选择一个块s作为我们拼出来的块,另一个块t代表我们此次操作拼上去的块,所以本次的贡献既是
代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=10;
int n;
int a[N][N],num[N][N],sum[N][N],dp[N][N][N][N][20];
int query(int i,int j,int x,int y){
int cnt=sum[x][y]+sum[i-1][j-1]-sum[i-1][y]-sum[x][j-1];
return cnt*cnt;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=8;i++){
for(int j=1;j<=8;j++){
scanf("%d",&a[i][j]);
num[i][j]=num[i][j-1]+a[i][j];
sum[i][j]=sum[i-1][j]+num[i][j];
}
}
memset(dp,0x3f3f3f,sizeof(dp));
for(int i=1;i<=8;i++){
for(int j=1;j<=8;j++){
for(int x=i;x<=8;x++){
for(int y=j;y<=8;y++){
dp[i][j][x][y][0]=query(i,j,x,y);
}
}
}
}
for(int k=1;k<n;k++){
for(int lenx=1;lenx<=8;lenx++){
for(int i=1;i+lenx-1<=8;i++){
for(int leny=1;leny<=8;leny++){
for(int j=1;j+leny-1<=8;j++){
int x=i+lenx-1,y=j+leny-1;
for(int g=i;g<x;g++){
dp[i][j][x][y][k]=min(dp[i][j][g][y][k-1]+query(g+1,j,x,y),dp[i][j][x][y][k]);
dp[i][j][x][y][k]=min(dp[g+1][j][x][y][k-1]+query(i,j,g,y),dp[i][j][x][y][k]);
}
for(int g=j;g<y;g++){
dp[i][j][x][y][k]=min(dp[i][j][x][g][k-1]+query(i,g+1,x,y),dp[i][j][x][y][k]);
dp[i][j][x][y][k]=min(dp[i][g+1][x][y][k-1]+query(i,j,x,g),dp[i][j][x][y][k]);
}
}
}
}
}
}
double xb=(double)sum[8][8]/(double)n;
// printf("%lf\n",xb);
printf("%.3lf",sqrt((double)dp[1][1][8][8][n-1]/(double)n-xb*xb));
}
T6:
为什么这么简单的题我都想不出来!!!
只能从两边删数可以看成从一个中心区间拓展到两个大区间的过程
我们可以对于区间
1.直接删去区间
2.分别删去
枚举k,正常转移即可
T7:
差一点就想到了啊啊啊啊啊啊啊
没有想起来区间dp要枚举k呜呜呜
贪心策略:
最有情况下,必定将一个狼一次性消灭,证明因为把一只狼消灭到一半再去攻击其他的狼受到伤害必定不小于一次性消灭掉的伤害
我们设
于是只要枚举攻击的狼k,受到的伤害为
转移式子
for(int k=i;k<=j;k++){
dp[i][j]=min(dp[i][j],dp[i][k-1]+dp[k+1][j]+(a[k]+b[i-1]+b[j+1])*h[k]);
}
注意初始化时要把所有状态设为inf
然后对于
注意因为我的代码还有一点比较特殊就是会访问到
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=405;
int n,atk;
int a[N],b[N],h[N],dp[N][N];
int main(){
scanf("%d%d",&n,&atk);
for(int i=1;i<=n;i++){
scanf("%d%d%d",&a[i],&b[i],&h[i]);
h[i]=(int)ceil((double)h[i]/(double)(atk));
}
memset(dp,0x3f3f3f3f,sizeof(dp));
for(int len=1;len<=n;len++){
for(int i=1;i+len-1<=n;i++){
int j=i+len-1;
if(len==1){
dp[i][j]=(a[i]+b[i-1]+b[i+1])*h[i];
dp[i][i-1]=dp[i+1][i]=0;
continue;
}
for(int k=i;k<=j;k++){
dp[i][j]=min(dp[i][j],dp[i][k-1]+dp[k+1][j]+(a[k]+b[i-1]+b[j+1])*h[k]);
}
}
}
printf("%d",dp[1][n]);
}
T8:
我们把一整块都为白色的贡献设为0,否则设为
然后二维区间dp转移即可
T9:
设
然后我们可以枚举上一维是由选哪两个人,得来的,可以枚举不选前一段男生或不选前一段女生的长度,具体转移式子可以参考ybt
这里为什么可以只枚举删去一段连续的女生或男生而不是两边都删呢,我们会发现这因该是包不优的,因为我们可以在两边都不选的一堆男生和女生中插进来一对,一定比之前更优
这里我之前有一个问题,就是这里为什么要选取边界条件设为-inf呢
for(int i=1;i<=n+1;i++){
dp[i][0]=dp[0][i]=-inf;
}
考虑首先在定义上它就不合法,
最终代码:
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=305,inf=1e18+5;
int n;
int a[N],suma[N],sumb[N],b[N],dp[N][N];
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
suma[i]=suma[i-1]+a[i];
}
for(int i=1;i<=n;i++){
scanf("%lld",&b[i]);
sumb[i]=sumb[i-1]+b[i];
}
suma[n+1]=suma[n];
sumb[n+1]=sumb[n];
for(int i=1;i<=n+1;i++){
dp[i][0]=dp[0][i]=-inf;
}
for(int i=1;i<=n+1;i++){
for(int j=1;j<=n+1;j++){
dp[i][j]=dp[i-1][j-1]+a[i]*b[j];
for(int k=1;k<i;k++){
dp[i][j]=max(dp[k-1][j-1]+a[i]*b[j]-(suma[i-1]-suma[k-1])*(suma[i-1]-suma[k-1]),dp[i][j]);
}
for(int k=1;k<j;k++){
dp[i][j]=max(dp[i-1][k-1]+a[i]*b[j]-(sumb[j-1]-sumb[k-1])*(sumb[j-1]-sumb[k-1]),dp[i][j]);
}
}
}
printf("%lld",dp[n+1][n+1]);
}
T10:
本人认为这道题一本通题解思考顺序有一点乱,没有逻辑性,以至于觉得它说的很对,但又感觉少了点什么,所以本人来整理一下
我们还是设
然后发现转移发现最终答案下传与统计只与最值有关系,所以设
其实还是有一点没有明白,就是为什么必须左边g,右边f,没有搞懂,挖坑待填
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探