区间 DP
一般基础状态:\(f(l,r)\) 表示区间为 \([l,r]\) 的答案,然后进行转移。
由于我比较懒,在不需要太多优化时喜欢写递归的区间 \(dp\)。
int dp(int l,int r){
if(f[l][r]) return f[l][r];
if(l==r) return f[l][r]==...;
for(int k=...;k<=...;k++) f[l][r]=...;
return f[l][r];
}
或者
int dp(int l,int r){
if(f[l][r]) return f[l][r];
if(l==r) return f[l][r]==...;
f[l][r]=...(f[l+1][r],f[l][r-1],...);
return f[l][r];
}
能量项链
自然状态 \(f(l,r)\),破环成链。
\[ f(l,r)=\max\limits_{l\le k<r}{f(l,k)+f(k+1,r)+a_la_ra_k}
\]
#include<bits/stdc++.h>
using namespace std;
const int N=109;
int n,a[N*3],f[N*2][N*2],ans;
int dfs(int u,int v){
if(f[u][v]) return f[u][v];
if(u==v) return f[u][v]=0;
for(int k=u;k<v;k++)f[u][v]=max(f[u][v],dfs(u,k)+dfs(k+1,v)+a[u]*a[v+1]*a[k+1]);
return f[u][v];
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),a[i+2*n]=(a[i+n]=a[i]);
for(int i=1;i<=n;i++) ans=max(ans,dfs(i,i+n-1));
printf("%d",ans);
return 0;
}
石子合并
自然状态,破环成链。 然后这题要算两次。
\(f\) 表示最大值。
\[f(l,r)=\max\limits_{l\le k<r}{f(l,k)+f(k+1,r)+\sum\limits_{i=l}^{r}a_i}
\]
#include<bits/stdc++.h>
using namespace std;
const int N=209;
int n,a[N],s[N],f[N][N],g[N][N],ans1,ans2=0x3f3f3f3f;
int dfs1(int l,int r){
if(f[l][r]) return f[l][r];
if(l==r) return f[l][r]=0;
for(int k=l;k<r;k++) f[l][r]=max(f[l][r],dfs1(l,k)+dfs1(k+1,r)+s[r]-s[l-1]);
return f[l][r];
}
int dfs2(int l,int r){
if(g[l][r]!=0x3f3f3f3f) return g[l][r];
if(l==r) return g[l][r]=0;
for(int k=l;k<r;k++) g[l][r]=min(g[l][r],dfs2(l,k)+dfs2(k+1,r)+s[r]-s[l-1]);
return g[l][r];
}
int main(){
scanf("%d",&n); memset(g,0x3f,sizeof(g));
for(int i=1;i<=n;i++) scanf("%d",&a[i]),a[i+n]=a[i];
for(int i=1;i<=2*n;i++) s[i]=s[i-1]+a[i];
for(int i=1;i<=n;i++) ans1=max(ans1,dfs1(i,i+n-1)),ans2=min(ans2,dfs2(i,i+n-1));
printf("%d\n%d",ans2,ans1);
return 0;
}
USACO16 248G
自然状态,要判断是否相等。
\[f(l,r)=\max\limits_{l\le k<r,f(l,k)=f(k+1,r)}{f(l,k)+1}
\]
#include<bits/stdc++.h>
#pragma optimize("Ofast,unroll-loop")
using namespace std;
const int N=259,ninf=-1e9;
int n,f[N][N],a[N],ans;
int dfs(int u,int v){
if(f[u][v]) return f[u][v]; f[u][v]=ninf;
if(u==v) return f[u][v]=a[u];
for(int k=u;k<=v-1;k++)
if(dfs(u,k)==dfs(k+1,v)) f[u][v]=max(f[u][v],dfs(u,k)+1);
return ans=max(ans,f[u][v]),f[u][v];
}
int main(){
scanf("%d",&n);
for(register int i=1;i<=n;++i) scanf("%d",&a[i]);
dfs(1,n),printf("%d",ans);
return 0;
}
USACO16 262144P
上题加强版,要做一个优化。
如果枚举 \(l,r\) 就需要枚举中间点 \(k\),是件非常耗时间的事情。考虑到其实合并的答案很小,我们答案状态转换,即 \(f(l,p)\) 表示 左端点为 \(l\),答案为 \(p\) 的右端点(可证明右端点唯一)
\[f(l,p)=f(f(l,p-1),p-1)
\]
按照倍增的循环方式,先循环 \(p\),再做 \(l\)。
#include<bits/stdc++.h>
using namespace std;
int n,a[262525],f[262525][65],ans;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),f[i][a[i]]=i+1;
for(int p=1;p<=59;p++){
for(int l=1;l<=n;l++){
if(!f[l][p]) f[l][p]=f[f[l][p-1]][p-1];
if(f[l][p]) ans=max(ans,p);
}
}
printf("%d",ans);
return 0;
}
[USACO06FEB]Treats for the Cows G/S
普通状态,第二种转移。一个水题。
#include<bits/stdc++.h>
using namespace std;
const int N=2009;
int n,a[N],s[N],f[N][N];
int dp(int l,int r){
if(f[l][r]) return f[l][r];
if(l==r) return f[l][r]=a[l];
f[l][r]=max(dp(l,r-1),dp(l+1,r))+s[r]-s[l-1];
return f[l][r];
}
int main(){
scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]),s[i]=s[i-1]+a[i];
printf("%d",dp(1,n));
return 0;
}
[CQOI2007] 染色
朴素状态。
转移采取一个稍微贪心的思想:如果 \(l,r\) 颜色一样,那么可以先不管其中一个端点,然后去做,即 \(f(l,r)=\min(f(l+1,r),f(l,r-1))\),因为我们本来就是一开始把这个区间全部染成这个相同的颜色,能染一次就满足首尾的颜色。
\[f(l,r)=\min\limits_{l\le k<r} f(l,k)+f(k+1,r)
\]
如果首尾相同:
\[f(l,r)=\min(f(l+1,r),f(l,r-1))
\]
#include<bits/stdc++.h>
using namespace std;
const int N=109,inf=0x3f3f3f3f;
int n,f[N][N]; char a[N];
int dp(int l,int r){
if(f[l][r]!=inf) return f[l][r];
if(l==r) return f[l][r]=1;
if(a[l]==a[r]) return f[l][r]=min(dp(l+1,r),dp(l,r-1));
for(int k=l;k<r;k++) f[l][r]=min(f[l][r],dp(l,k)+dp(k+1,r));
return f[l][r];
}
int main(){
memset(f,0x3f,sizeof(f));
scanf("%s",a+1); n=strlen(a+1);
printf("%d",dp(1,n));
return 0;
}
[IOI1998] Polygon
朴素状态。
对于加法,肯定是选择两个大的相加。
对于乘法,可能遇到两个负数相乘边为正数然后碾压正数的情况。对此,我们记录最小值,每次拿 \(4\) 个状态
(即
\[f(l,k)g(r+1,k),f(l,k)g(k+1,r),g(l,k)g(k+1,r),g(l,k)f(k+1,r)
\]
)
去比较。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=509,inf=1e18;
char e[N]; int n,a[N],f[N][N],g[N][N],mx,ans[N];
int dp(int l,int r){
if(f[l][r]!=-inf) return f[l][r];
if(l==r) return f[l][r]=g[l][r]=a[l];
for(int k=l;k<r;k++){
dp(l,k),dp(k+1,r);
if(e[k+1]=='t')
f[l][r]=max(f[l][r],f[l][k]+f[k+1][r]),g[l][r]=min(g[l][r],g[l][k]+g[k+1][r]);
else
f[l][r]=max(f[l][r],max(f[l][k]*f[k+1][r],
max(g[l][k]*g[k+1][r],max(f[l][k]*g[k+1][r],g[l][k]*f[k+1][r])))),
g[l][r]=min(g[l][r],min(g[l][k]*g[k+1][r],
min(f[l][k]*f[k+1][r],min(f[l][k]*g[k+1][r],g[l][k]*f[k+1][r]))));
}
return f[l][r];
}
signed main(){
scanf("%lld",&n);
for(int i=1;i<=2*n;i++) for(int j=1;j<=2*n;j++) f[i][j]=-inf,g[i][j]=inf;
for(int i=1;i<=n;i++) cin>>e[i]>>a[i],e[i+n]=e[i],a[i+n]=a[i];
for(int i=1;i<=n;i++){
ans[i]=dp(i,n+i-1); mx=max(mx,ans[i]);
}
printf("%lld\n",mx);
for(int i=1;i<=n;i++) if(ans[i]==mx) printf("%lld ",i);
return 0;
}
[HNOI2010] 合唱队
如果用自然状态,我们会发现无法确定现在应该选左边还是右边(因为受到上一个选的影响)。
于是我们定义 \(f(l,r,0/1)\) 为 \([l,r]\) 中现在取左/右。
对于 \(l=r\) 的情况,由于只能算一次,于是我们规定 \(l=r\) 的话只能选右边(只能选左边也行)。
#include<bits/stdc++.h>
using namespace std;
const int N=1009,mod=19650827;
int n,a[N],f[N][N][2],v[N][N][2],tick;
int dp(int l,int r,int status){
if(v[l][r][status]) return f[l][r][status]; v[l][r][status]=1;
if(l==r) return f[l][r][status]=!status;
if(status==0){
if(a[l]<a[l+1]) f[l][r][0]+=dp(l+1,r,0);
if(a[l]<a[r]) f[l][r][0]+=dp(l+1,r,1);
}else{
if(a[r]>a[r-1]) f[l][r][1]+=dp(l,r-1,1);
if(a[r]>a[l]) f[l][r][1]+=dp(l,r-1,0);
} return f[l][r][status]%=mod;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
printf("%d\n",(dp(1,n,0)+dp(1,n,1))%mod);
return 0;
}
[USACO19DEC]Greedy Pie Eaters P
自然状态
\[f(l,r)=\max(\max f(l,k)+f(k+1,r),\max f(l,k-1)+f(k=1,r)+p(k,l,r))
\]
其中 \(p\) 表示吃的在 \(l,r\) 区间内包括 \(k\) 点的最大体重的牛的体重。
#include<bits/stdc++.h>
using namespace std;
const int N=309;
int n,m,w,l,r,p[N][N][N],f[N][N];
bool vst[N][N][N],v[N][N];
int dpp(int k,int l,int r){
if(vst[k][l][r]) return p[k][l][r]; vst[k][l][r]=1;
if(l==r) return p[k][l][r];
if(r<k||l>k) return 0;
if(r>1) p[k][l][r]=max(p[k][l][r],dpp(k,l,r-1));
if(l<n) p[k][l][r]=max(p[k][l][r],dpp(k,l+1,r));
return p[k][l][r];
}
int dp(int l,int r){
if(v[l][r]) return f[l][r]; v[l][r]=1;
if(l>r) return 0;
for(int k=l;k<r;k++) f[l][r]=max(f[l][r],dp(l,k)+dp(k+1,r));
for(int k=l;k<=r;k++){
f[l][r]=max(f[l][r],dp(l,k-1)+dp(k+1,r)+p[k][l][r]);
}
return f[l][r];
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&w,&l,&r);
for(int j=l;j<=r;j++) p[j][l][r]=w;
}
for(int k=1;k<=n;k++) dpp(k,1,n);
printf("%d",dp(1,n));
return 0;
}