持续更新…
1.P1005 矩阵取数
解题思路:
按行区间dp
- 设
f
[
l
]
[
r
]
f[l][r]
f[l][r]每行从
l
l
l到
r
r
r的符合条件的和,那么根据只能从左边或右边取数,有:
f [ l ] [ r ] = m a x ( f [ l + 1 ] [ r ] + a [ l ] ∗ p [ k ] , f [ l ] [ r − 1 ] + a [ r ] ∗ p [ k ] ) f[l][r]=max(f[l+1][r]+a[l]*p[k],f[l][r-1]+a[r]*p[k]) f[l][r]=max(f[l+1][r]+a[l]∗p[k],f[l][r−1]+a[r]∗p[k])
这里的 k k k表示2的幂指数,联系到 k k k与已经取走的石子个数之间的关系,所以 k = m − ( r − l + 1 ) + 1 = m − ( r − l ) k=m-(r-l+1)+1=m-(r-l) k=m−(r−l+1)+1=m−(r−l),其中 m − ( r − l + 1 ) m-(r-l+1) m−(r−l+1)表示已经取走的石子的个数,由于最开始没有取走任何石子时, k k k为1,所以需要在取走的石子个数上加1。 - 其次就是注意可能爆long long,需要用__int128来存储。输出__int128格式可采用如下:
void print(__int128 x){
if(x>9) print(x/10);
putchar('0'+x%10);
}
代码部分
#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
__int128 map[85][85],f[85][85];
__int128 ans=0;
//按行区间dp
__int128 p[85];
int n,m;
__int128 dfs(int l,int r,__int128 a[]){
if(l>r) return 0;
if(f[l][r]) return f[l][r];
f[l][r]=max(dfs(l+1,r,a)+a[l]*p[m-(r-l)],dfs(l,r-1,a)+a[r]*p[m-(r-l)]);
return f[l][r];
}
void print(__int128 x){
if (x>9) print(x/10);
putchar('0'+x%10);
}
int main(int argc, char** argv) {
scanf("%d%d",&n,&m);
__int128 ans=0, t=1;
p[0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",&map[i][j]);
if(i==1) p[j]=p[j-1]*2;
}
}
for(int i=1;i<=n;i++){
memset(f,0,sizeof(f));//初始化
__int128 tmp=dfs(1,m,map[i]);
ans+=tmp;
}
print(ans);
return 0;
}
2.P1880 石子合并
解题思路:
最初拿到题目的时候,以为是用赫夫曼树的思想合并石子。但是这个合并的顺序是一定的,即只能和左边或者右边的石子合并,而不是从一堆石子中找出最小的两堆石子合并。在解题过程中需注意:
- 石子排列成环形,如何对环形的石子进行每轮状态的更新?(利用将数组扩展成 2 n 2n 2n,将环形化成链状结构,方便下一步处理。注意保存的数组大小至少开到 2 n 2n 2n。)
- 由于当前石子只能和左右的石子合并,化成链状之后就可以转化为区间dp问题,这里设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示从
i
i
i到
j
j
j个石子获得的总分,那么在合并石子的最大值
f
[
i
]
[
j
]
f[i][j]
f[i][j]时:
f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i ] [ k ] + f [ k + 1 ] [ j ] + d ( i , j ) ) f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+d(i,j)) f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+d(i,j)),其中 d ( i , j ) d(i,j) d(i,j)为从 i i i到 j j j的前缀和,之所以加上 d ( i , j ) d(i,j) d(i,j)是因为最终合并的过程包括从 i i i到 j j j的前缀和。 - 利用记忆化搜索减少复杂度。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#define mx 233233233
using namespace std;
int a[220],b[220];
//f[i][j]= max(f[i][j],f[i][k]+f[k][j]+d[i][j])
int f[220][220],f2[220][220];
int d(int i,int j){
return b[j]-b[i-1];//i到j的前缀和
}
int dfs(int l,int r){
if(l==r) return 0;
if(f[l][r]!=0) return f[l][r];
for(int k=l;k<r;k++){
f[l][r]=max(f[l][r],dfs(l,k)+dfs(k+1,r)+d(l,r));
}
return f[l][r];
}
int dfs2(int i,int j){
if(i==j) return 0;
if(f2[i][j]!=mx) return f2[i][j];
for(int k=i;k<j;k++){
f2[i][j]=min(f2[i][j],dfs2(i,k)+dfs2(k+1,j)+d(i,j));
}
// printf("%d\n",f2[i][j]);
return f2[i][j];
}
int main(int argc, char** argv) {
int N;
int ans=-1,ans2=mx;
scanf("%d",&N);
for(int i=1;i<=N;i++){
scanf("%d",&a[i]);
a[i+N]=a[i];
}
for(int i=1;i<=2*N;i++){
b[i]=b[i-1]+a[i];//化成链状
for(int j=1;j<=2*N;j++){
f[i][j]=0;f2[i][j]=mx;
}
}
dfs(1,2*N);dfs2(1,2*N);
for(int i=1;i+N<=2*N;i++){
ans2=min(ans2,f2[i][i+N-1]);
ans=max(ans,f[i][i+N-1]);
}
printf("%d\n%d\n",ans2,ans);
return 0;
}
3.P1063能量项链
解题思路:
与P1880 石子合并有异曲同工之妙(因为都是区间dp问题),注意到合并时并非最开始想的从左到右顺序合并,在这里记
f
[
l
]
[
r
]
f[l][r]
f[l][r]为从
l
l
l到
r
r
r合并的总能量,那么有:
f
[
l
]
[
r
]
=
m
a
x
(
f
[
l
]
[
r
]
,
f
[
l
]
[
k
]
+
f
[
k
+
1
]
[
r
]
+
a
[
l
]
∗
a
[
k
+
1
]
∗
a
[
r
+
1
]
)
f[l][r]=max(f[l][r],f[l][k]+f[k+1][r]+a[l]*a[k+1]*a[r+1])
f[l][r]=max(f[l][r],f[l][k]+f[k+1][r]+a[l]∗a[k+1]∗a[r+1])
其中
a
[
l
]
a[l]
a[l]为左端点,
a
[
k
+
1
]
a[k+1]
a[k+1]为第一部分的右端点,也是第二部分的左端点,
a
[
r
+
1
]
a[r+1]
a[r+1]为第二部分的右端点。然后将环化成长度为
2
n
2n
2n的链即可。
#include <iostream>
#include<cstdio>
using namespace std;
#define ll long long
ll a[110+110];
ll f2[220][220];
int dfs(int l,int r){
if(l>=r) return 0;//搜索终点,l==r
if(f2[l][r]) return f2[l][r];//记忆化搜索
for(int k=l;k+1<=r;++k){
f2[l][r]=max(f2[l][r],dfs(l,k)+dfs(k+1,r)+a[k+1]*a[l]*a[r+1]);
}
return f2[l][r];
}
int main(int argc, char** argv) {
int n;scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
a[i+n]=a[i];
}
dfs(1,2*n-1);//只能到2*n-1,为了保证递推公式里面的a[r+1]不超过a[2*n]
ll ans=-1;
for(int i=1;i<=n;i++){
ans=max(ans,f2[i][i+n-1]);
}
printf("%lld",ans);
return 0;
}