【每周算法】第十三周:区间DP
有点死了,没几个不看题解(或问AI或看视频课)会的
笔记
对于区间问题,可以考虑使用区间dp。区间dp的阶段一般有两种方法划分:
- 枚举区间开头和结尾
- 枚举区间长度和区间开头(或结尾),计算出区间的结尾(或开头)
同时,如果遇到环dp问题,可以尝试断环为链(可以将数组复制一遍接在后面)
Luogu P1004 [NOIP 2000 提高组] 方格取数
这题和那个传纸条那题有些类似,但不同,这个是两条路线可以有交点的
#include<bits/stdc++.h>
using namespace std;
const int N = 20;
int a[N][N],f[N][N][N][N];
int main(){
int n;
scanf("%d",&n);
while(true){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
if(x == 0 && y == 0 && z == 0){
break;
}
a[x][y] = z;
}
for(int i = 1;i <= n;i++){
for(int j = 1;j <= n;j++){
for(int k = 1;k <= n;k++){
for(int l = 1;l <= n;l++){
f[i][j][k][l] = max({f[i-1][j][k-1][l],f[i][j-1][k-1][l],f[i-1][j][k][l-1],f[i][j-1][k][l-1]})+a[i][j]+a[k][l];
if(i == k && j == l){
// 由于只能往下或往右,所以如果两条路径经过同一个点,则一定是重合的两条路径
f[i][j][k][l] -= a[i][j];
}
}
}
}
}
printf("%d",f[n][n][n][n]);
return 0;
}
Luogu P1435 [IOI 2000] 回文字串
问了以下deepseek。这题还是有一点意思的。
我最初的思路是将字符串分割成左右两个部分,将一个部分翻转一下,然后做只有插入的编辑距离,但我似乎把这题想单纯了,然后我就想明白了这样不行,然后问了deepseek。
定义:为字符串,为字符串的长度
- 阶段:按照子串长度划分
- 状态:的部分转换为回文串至少插入的字符个数
- 转移方程:分两种情况讨论:
- ,则
- ,则
- 边界:此题似乎没有边界
最后输出即可
#include<bits/stdc++.h>
using namespace std;
const int N= 1010;
char s[N];
int f[N][N];
int main(){
scanf("%s",s);
int n = strlen(s);
for(int k = 2;k <= n;k++){
// k表示子串长度
for(int i = 0;i < n;i++){
int j = i+k-1;
if(j >= n){
break;
}
if(s[i] == s[j]){
f[i][j] = f[i+1][j-1];
} else{
f[i][j] = min(f[i+1][j],f[i][j-1])+1;
}
// printf("f[%d][%d] = %d\n",i,j,f[i][j]);
}
}
printf("%d",f[0][n-1]);
return 0;
}
Luogu P1775 石子合并(弱化版)
定义:表示读入的序列,表示序列的长度
- 阶段:和上题类似,就是必须按照长度遍历
- 状态:合并的最小代价
- 转移方程:
- 边界:这题似乎也没有边界
#include<bits/stdc++.h>
using namespace std;
const int N = 310;
int m[N],f[N][N];
int main(){
int n;
scanf("%d",&n);
for(int i = 1;i <= n;i++){
scanf("%d",&m[i]);
}
for(int k = 2;k <= n;k++){
for(int i = 1;i <= n;i++){
int j = i+k-1,sum = 0;
if(j > n){
break;
}
f[i][j] = INT_MAX;
for(int l = i;l <= j;l++){
f[i][j] = min(f[i][j],f[i][l]+f[l+1][j]);
sum += m[l];
}
f[i][j] += sum;
}
}
printf("%d",f[1][n]);
return 0;
}
Luogu P1220 关路灯
也是看了视频课好吧。
对于关到某个时间节点,我们会发现,如果所关的部分不是连续的,则一定不是最优的,所以就可以把这题转换为区间问题。
- 阶段:从中间往两边遍历
- 状态:表示关闭从至的区间且最后人在左边(即上)的最小代价。表示关闭从至的区间且最后人在右边(即上)的最小代价
- 转移方程:有点长,就直接看代码吧
- 边界:当并且时,(由于这题需要将的所有元素设置为,所以这种情况是需要考虑的)
#include<bits/stdc++.h>
using namespace std;
const int N = 55;
int m[N],w[N];
int f[N][N][2];
int pre[N],sub[N];
int main(){
memset(f,0x3f,sizeof f);
int n,c;
scanf("%d%d",&n,&c);
for(int i = 1;i <= n;i++){
scanf("%d%d",&m[i],&w[i]);
pre[i] = pre[i-1]+w[i];
}
for(int i = n;i >= 1;i--){
sub[i] = sub[i+1]+w[i];
}
for(int l = c;l >= 1;l--){
for(int r = c;r <= n;r++){
if(l == c && r == c){
f[l][r][0] = 0;
f[l][r][1] = 0;
} else{
// 转移方程
if(l != c){
f[l][r][0] = min({f[l+1][r][0]+(m[l+1]-m[l])*(pre[l]+sub[r+1]),f[l+1][r][1]+(m[r]-m[l])*(pre[l]+sub[r+1])});
}
if(r != c){
f[l][r][1] = min({f[l][r-1][1]+(m[r]-m[r-1])*(pre[l-1]+sub[r]),f[l][r-1][0]+(m[r]-m[l])*(pre[l-1]+sub[r])});
}
}
// printf("f[%d][%d][0] = %d\n",l,r,f[l][r][0]);
// printf("f[%d][%d][1] = %d\n\n",l,r,f[l][r][1]);
}
}
printf("%d",min(f[1][n][0],f[1][n][1]));
return 0;
}
Luogu P4342 [IOI 1998] Polygon
这题有点复杂,不太会
Luogu P1880 [NOI1995] 石子合并
我们只需要对那个弱化版做一个环处理即可(哦,还要处理最大值)
#include<bits/stdc++.h>
using namespace std;
const int N = 210;
int m[N],f[N][N],g[N][N];
int main(){
int n;
scanf("%d",&n);
for(int i = 1;i <= n;i++){
scanf("%d",&m[i]);
m[n+i] = m[i];
}
for(int k = 2;k <= 2*n;k++){
for(int i = 1;i <= 2*n;i++){
int j = i+k-1,sum = 0;
if(j > 2*n){
break;
}
f[i][j] = INT_MAX;
for(int l = i;l <= j;l++){
f[i][j] = min(f[i][j],f[i][l]+f[l+1][j]);
g[i][j] = max(g[i][j],g[i][l]+g[l+1][j]);
sum += m[l];
}
f[i][j] += sum;
g[i][j] += sum;
}
}
int mx = 0,mn = INT_MAX;
for(int i = 1;i <= n;i++){
mn = min(mn,f[i][i+n-1]);
mx = max(mx,g[i][i+n-1]);
}
printf("%d\n%d",mn,mx);
return 0;
}
Luogu P6064 [USACO05JAN] Naptime G
这题呢其实细想还是蛮好做的,不过我还是看了题解,又对着AI调试
是看了这篇题解,思路就不想说了
#include<bits/stdc++.h>
using namespace std;
const int N = 4000;
int u[N],f[N][N][2];
int n,b,ans;
void dp(){
for(int i = 2;i <= n;i++){
f[i][0][0] = 0;
for(int j = 1;j <= b;j++){
f[i][j][0] = max(f[i-1][j][0],f[i-1][j][1]);
f[i][j][1] = max(f[i-1][j-1][0],f[i-1][j-1][1]+u[i]);
// printf("f[%d][%d][0] = %d\n",i,j,f[i][j][0]);
// printf("f[%d][%d][1] = %d\n\n",i,j,f[i][j][1]);
}
}
// ans = max({ans,f[n][b][1],f[n][b][0]});
}
int main(){
scanf("%d%d",&n,&b);
for(int i = 1;i <= n;i++){
scanf("%d",&u[i]);
}
memset(f,-0x3f,sizeof f);
f[1][1][1] = 0;
f[1][0][0] = 0;
dp();
ans = max({ans,f[n][b][1],f[n][b][0]});
memset(f,-0x3f,sizeof f);
f[1][1][1] = u[1];
f[1][0][0] = 0;
dp();
ans = max({ans,f[n][b][1]});
printf("%d",ans);
return 0;
}
/*
7 6
150000
160000
170000
140000
180000
190000
200000
ans: 880000
*/