区间dp学习笔记
回归后练的第一个专题,也是写的第一篇整理
区间 dp 首先需要区间可以合并,因此通常可以枚举拆开来的点
这里的拆我们可以分成三种
第一种是最为常规的,直接在中间枚举拆分的点即可,通常范围是 \(n\le 500\) ,如果 \(n\le50\) 可能需要再设计一维的状态
第二种则是选择拆掉最左边或者最右边的点,此时 \(n\le 5000\) 或者根据状态而定,有时也可能只决策一个端点,然后对中间区间的长度进行一个决策,从而实现跨区间的两块区域之间的转移
第三种则是依据题目要求,选择区间内的某一段区间,在其中断点。
由于比较复杂,不可能全部讲完,那么直接看题目。
chorus 合唱队
最板子的一集,直接枚举左端点还是右端点,判断是否合法,转移即可
感觉记搜更好写
代码:
#include<bits/stdc++.h>
using namespace std;
const int mod=19650827;
int n,a[1005],dp[1005][1005][2];
bool vis[1005][1005][2];
int dfs(int l,int r,int op){
if(l==r){
if(op==0&&a[l-1]<a[l]||op==1&&a[r+1]>a[r])return 1;
return 0;
}
if(vis[l][r][op])return dp[l][r][op];
vis[l][r][op]=1;
int &res=dp[l][r][op];
if(op==0){
if(a[l-1]<a[l])res+=dfs(l+1,r,0);
if(a[l-1]<a[r])res+=dfs(l,r-1,1);
}
else {
if(a[r+1]>a[l])res+=dfs(l+1,r,0);
if(a[r+1]>a[r])res+=dfs(l,r-1,1);
}
res%=mod;
return res;
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
cout<<(dfs(1,n-1,1)+dfs(2,n,0))%mod;
return 0;
}
玩具取名
也是比较偏板子的枚举中间点进行分裂的问题
代码:
#include<bits/stdc++.h>
using namespace std;
int a[10],b[205];
char s[205];
int id(char x){
if(x=='W')return 1;
if(x=='I')return 2;
if(x=='N')return 3;
if(x=='G')return 4;
}
vector<pair<int,int>> e[10];
bool vis[205][205][4],dp[205][205][4];
bool dfs(int l,int r,int w){
if(l==r){
if(b[l]==w)return 1;
return 0;
}
if(vis[l][r][w])return dp[l][r][w];
vis[l][r][w]=1;
bool &res=dp[l][r][w];
for(int i=l;i<r;i++){
for(auto tmp:e[w]){
if(dfs(l,i,tmp.first)&&dfs(i+1,r,tmp.second))return res=1;
}
}
return res=0;
}
signed main(){
for(int i=1;i<=4;i++)cin>>a[i];
for(int i=1;i<=4;i++){
for(int j=1;j<=a[i];j++){
char op1,op2;
cin>>op1>>op2;
e[i].push_back({id(op1),id(op2)});
}
}
cin>>s+1;
int len=strlen(s+1);
for(int i=1;i<=len;i++)b[i]=id(s[i]);
bool f=0;
if(dfs(1,len,1))cout<<'W',f=1;
if(dfs(1,len,2))cout<<'I',f=1;
if(dfs(1,len,3))cout<<'N',f=1;
if(dfs(1,len,4))cout<<'G',f=1;
if(!f)cout<<"The name is wrong!";
return 0;
}
方块消除
这个比较有难度了,是上述确定了右端点情况再确定中间区间的板子
通过先确定右端点,再删掉中间的,将右端点计入到左边部分的状态中去,从而实现代码
代码(还在记搜):
#include<bits/stdc++.h>
using namespace std;
int n,a[205];
bool vis[205][205][205];
int dp[205][205][205];
int dfs(int l,int r,int s){
if(l>r)return 0;
if(vis[l][r][s])return dp[l][r][s];
vis[l][r][s]=1;
int &res=dp[l][r][s];
res=dfs(l,r-1,0)+(s+1)*(s+1);
for(int i=l;i<r;i++)if(a[i]==a[r])res=max(res,dfs(l,i,s+1)+dfs(i+1,r-1,0));
return res;
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
cout<<dfs(1,n,0);
return 0;
}
压缩
比较有意思的一道题目,整体仍然是枚举断点,同时也要增加一维是否实现复制,加上这一维之后对两种情况分别进行转移即可
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
char s[105];
bool vis[105][105][2];
int dp[105][105][2];
int dfs(int l,int r,bool w){
if(l==r)return 2;
if(vis[l][r][w])return dp[l][r][w];
vis[l][r][w]=1;
int &res=dp[l][r][w];
if(w){
res=r-l+2;
bool f=1;
if(!(r-l&1))f=0;
int mid=l+r>>1;
for(int i=l;i<=mid;i++)if(s[i]!=s[mid+i-l+1])f=0;
if(f)res=min(res,dfs(l,mid,1)+1);
for(int i=l;i<r;i++)res=min(res,dfs(l,i,1)+r-i);
}
else {
res=1e18;
for(int i=l;i<r;i++){
res=min(res,dfs(l,i,0)+dfs(i+1,r,0));
if(i+1<r)res=min(res,dfs(l,i,0)+dfs(i+1,r,1));
if(l<i)res=min(res,dfs(l,i,1)+dfs(i+1,r,0));
if(i+1<r&&l<i)res=min(res,dfs(l,i,1)+dfs(i+1,r,1));
}
}
return res;
}
signed main(){
cin>>s+1;
cout<<min(dfs(1,strlen(s+1),0),dfs(1,strlen(s+1),1))-1;
return 0;
}
字符串折叠
相比较上面一题而言就没什么新意了,挺水,直接暴力枚举断点分割,然后对整块进行压缩即可。
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
char s[105];
bool vis[105][105];
int dp[105][105];
int getlen(int x){
int s=0;
while(x){
s++;
x/=10;
}
return s;
}
int dfs(int l,int r){
if(vis[l][r])return dp[l][r];
vis[l][r]=1;
int &res=dp[l][r];
res=r-l+1;
for(int i=l;i<r;i++)res=min(res,dfs(l,i)+dfs(i+1,r));
for(int i=l;i<=r;i++){
if((r-l+1)%(i-l+1)!=0)continue;
bool f=1;
for(int j=l,k=i+1;k<=r;k++){
if(s[k]!=s[j]){
f=0;
break;
}
j++;
if(j>i)j=l;
}
if(f)res=min(res,getlen((r-l+1)/(i-l+1))+2+dfs(l,i));
}
return res;
}
signed main(){
cin>>s+1;
cout<<dfs(1,strlen(s+1));
return 0;
}
涂色paint
非常常见的涂色题目,我们一般将左右两端的颜色选择一种作为底色,然后再进行分割,每次可以选择继续分割还是切换底色,在只有一个点时判断是否和底色相同即可
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
char s[55];
int a[55];
bool vis[55][55][30];
int dp[55][55][30];
int dfs(int l,int r,int c){
if(l==r){
if(a[l]==c)return 0;
return 1;
}
if(vis[l][r][c])return dp[l][r][c];
vis[l][r][c]=1;
int &res=dp[l][r][c];
res=min(dfs(l+1,r,a[l])+1,dfs(l,r-1,a[r])+1);
for(int i=l;i<r;i++)res=min(res,dfs(l,i,c)+dfs(i+1,r,c));
return res;
}
signed main(){
cin>>s+1;
for(int i=1;i<=strlen(s+1);i++)a[i]=s[i]-'A'+1;
cout<<dfs(1,strlen(s+1),0);
return 0;
}
zh_tree
直接把中序遍历看成中间选择一个点作为根,然后分开来即可
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,d[35],pre[35];
bool vis[35][35];
int dp[35][35];
double k,c;
int dfs(int l,int r){
if(l>r)return 0;
if(l==r)return d[l];
if(vis[l][r])return dp[l][r];
vis[l][r]=1;
int &res=dp[l][r];
res=1e18;
for(int i=l;i<=r;i++)res=min(res,dfs(l,i-1)+dfs(i+1,r)+pre[r]-pre[l-1]);
return res;
}
signed main(){
cin>>n>>k>>c;
for(int i=1;i<=n;i++)cin>>d[i];
for(int i=1;i<=n;i++)pre[i]=pre[i-1]+d[i];
printf("%.3lf",dfs(1,n)*k/pre[n]+c);
return 0;
}
Grazing on the Run
非常常见的套路了
先进行离散化
然后再记录在区间左边还是右边即可
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,a[1005],dp[1005][1005][2];
bool vis[1005][1005][2];
int dfs(int l,int r,int w){
if(l==1&&r==n)return 0;
if(vis[l][r][w])return dp[l][r][w];
vis[l][r][w]=1;
int &res=dp[l][r][w];
int p;
if(w)p=a[r];
else p=a[l];
res=1e18;
if(l!=1)res=min(res,dfs(l-1,r,0)+(p-a[l-1])*(n-(r-l+1)));
if(r!=n)res=min(res,dfs(l,r+1,1)+(a[r+1]-p)*(n-(r-l+1)));
return res;
}
signed main(){
cin>>n>>a[1];
int p=a[1];
n++;
for(int i=2;i<=n;i++)cin>>a[i];
sort(a+1,a+n+1);
for(int i=1;i<=n;i++){
if(a[i]==p){
cout<<dfs(i,i,0);
return 0;
}
}
return 0;
}
beetle
还算比较有意思的题目
比起常规的区间dp增加了水流完了以后就不会对贡献产生影响
所以解决这一问题,方法是枚举会前往多少个位置,这样错解不优,不会产生影响
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,a[305],dp[305][305][2];
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
a[++n]=0;
sort(a+1,a+n+1);
int p=lower_bound(a+1,a+n+1,0)-a;
int ans=0;
for(int i=1;i<=n;i++){
memset(dp,0x3f,sizeof(dp));
dp[p][p][0]=dp[p][p][1]=0;
for(int len=1;len<=i;len++){
for(int l=1;l+len-1<=n;l++){
int r=l+len-1;
if(l>1)dp[l-1][r][0]=min({dp[l-1][r][0],dp[l][r][0]+(i-len)*(a[l]-a[l-1]),dp[l][r][1]+(i-len)*(a[r]-a[l-1])});
if(r<n)dp[l][r+1][1]=min({dp[l][r+1][0],dp[l][r][0]+(i-len)*(a[r+1]-a[l]),dp[l][r][1]+(i-len)*(a[r+1]-a[r])});
}
}
for(int l=1;l+i-1<=n;l++)ans=max(ans,(i-1)*m-dp[l][l+i-1][0]);
for(int l=1;l+i-1<=n;l++)ans=max(ans,(i-1)*m-dp[l][l+i-1][1]);
}
cout<<ans;
return 0;
}

浙公网安备 33010602011771号