P1437 [HNOI2004] 敲砖块 题解
初拿到手感觉限制太多了,不好硬做,于是开始观察。
乱想
若要取某一个数,我们要取其左上右上两个数,而这两个数又要取上面三个数,所以取一个数的前提条件其实是取这一个三角形。
举例
2 2 3 4 5
8 2 7 12
2 3 6
4 9
3
比如我要取第3行的6,我首先要取7和12,要取7和12,首先要取3,4,5,所以一层层拓展下去形成一个与整个形状相似的三角形。
形式化总结一下,如果我要取
所以自然想到做一个前缀和,表示这一个三角形的权值和,然后问题转化成了取哪些点能让我们的和最大。
考虑我选点不是无限制的,是
当前点所代表的三角形内数的个数是等差数列求和,即
而如果选的另外的点有重复,还要减去这些重复的点和权值,设这些点的数量为
那么有状态转移方程():
if(k>(i+1)*i/2-num) dp[i][j][k]=max(dp[i][j][k],dp[i][j][k-(i+1)*i/2+num]+a[i][j]-sum);
这是最直接便于口糊的办法,然而我们根本就是错的不知道哪里重复了。
转化
所以我们尝试对图形进行进一步的抽象总结。
我们看到多个三角形叠起来会形成一些折线,如图。
再把图搞优美一点 还是很丑qaq
7 \4 \5 3/ 2/ 4 5
1 \3 \6/ 7/ 3 4
5 \8/ \7/ 3 1
2 5 7 3
6 9 2
2 3
9
去掉重复则是
7 \4 5 3 2/ 4 5
1 \3 6 7/ 3 4
5 \8/ \7/ 3 1
2 5 7 3
6 9 2
2 3
9
我们发现这其实就转化成了一个用给定数量的线段围出一段波浪形,要使这一段的值最大的问题。那么我们的转移显然跟当前取的坐标、取到了第几个有关,所以我们可以设计三维
然后我们考虑那个前缀和现在要转化成什么,当前考虑到第
则有
注意
- 只在第一行统计答案,避免不合法。
- 先枚举列再枚举行,否则会造成左下的转移没有值。
- 初始值为
,其他设成负无穷,避免不合法转移。 - 各种变量的范围,行可以不取,即可以为
,列不行。 - 要用
枚举转折点。
Code
#include<bits/stdc++.h>
using namespace std;
int n,m,a[51][51],b[51][51];
long long dp[52][52][1280],maxi;
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=n;++i){
for(int j=1;j<=n-i+1;++j){
scanf("%d",&b[i][j]);
a[i][j]=a[i-1][j]+b[i][j];
}
}
memset(dp,-0x3f,sizeof(dp));
dp[0][0][0]=0;
for(int j=1;j<=n;++j){
for(int i=0;i+j<=n+1;++i){
for(int l=0;l<=i+1;++l){
for(int k=0;k<=m;++k){
if(k>=i) dp[i][j][k]=max(dp[i][j][k],dp[l][j-1][k-i]+a[i][j]);
}
}
}
}
for(int i=1;i<=n;++i) maxi=max(dp[1][i][m],maxi);
printf("%lld",maxi);
return 0;
}
时间复杂度 ,但是我懒。
upd on 2024.11.13
因为同学问了
我们发现状态数是
什么意思呢,发现我们其实没有必要枚举前面的转折点,直接同步维护一个前面所有能转移中的最大的进行转移就可以了。
具体地,我们维护一个
这里范围可能不是很对,精细的范围见代码。
Code
#include<bits/stdc++.h>
using namespace std;
int n,m,a[51][51],b[51][51];
long long dp[52][52][1280],s[52][52][1280],maxi;
inline int read(){
char ch;int x=0,f=1;
while(!isdigit(ch=getchar())){if(ch=='-')f=-1;}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;++i){
for(int j=1;j<=n-i+1;++j){
b[i][j]=read(),a[i][j]=a[i-1][j]+b[i][j];
}
}memset(dp,-1,sizeof(dp));dp[0][0][0]=0;
for(int j=1;j<=n;++j){
for(int i=0;i+j<=n+1;++i){
for(int k=0;k<=m;++k){
if(k>=i) dp[i][j][k]=s[i+1][j-1][k-i]+a[i][j];
s[i][j][k]=max(s[max(0,i-1)][j][k],dp[i][j][k]);
}
}
}printf("%lld",max(dp[0][n][m],dp[1][n][m]));
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端