T1
令 \(dp_{i,j}\) 表示卖出区间 \([i,j]\) 能获得的最大价值。
显然答案为 \(dp_{1,n}\)。
因为只能卖 \(i\) / \(j\),所以有转移:
初始:\(dp_{i,i}=v_i \times n\),其余为 \(- \infty\)。
code
#include<bits/stdc++.h>
using namespace std;
const int N=2e3+5;
int n;
int v[N];
int dp[N][N];
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>v[i];
memset(dp,0xcf,sizeof(dp));
for(int i=1;i<=n;i++) dp[i][i]=v[i]*n;
for(int i=2;i<=n;i++){
for(int j=1;j+i-1<=n;j++){
int s=j,e=j+i-1;
dp[s][e]=max(dp[s][e-1]+v[e]*(n-i+1),dp[s+1][e]+v[s]*(n-i+1));
}
}
cout<<dp[1][n];
return 0;
}
T2
我们注意到,对于字符串中的一个非对称位置 \(i\),
在对应位置添加 \(s_i\) 或删除 \(s_i\) 是等价的。
于是对于每个字符,它的操作代价即为 \(\min(\)添加代价\(,\)删除代价\()\)。
然后这题显然是个区间 dp。
因此有状态:令 \(dp_{i,j}\) 表示 将 \([i,j]\) 改为回文串的最小代价,答案为 \(dp_{1,m}\)。
接着我们发现这题显然不能用枚举中转点的套路,
因为即使 \([i,k]\) 与 \([k+1,j]\) 均为回文串,也不能保证 \([i,j]\) 为回文串。
于是我们考虑对端点进行转移,
具体来说,我们有:
因为我们是从短到长枚举区间,所以此转移的正确性可以保证。
同时,若 \(s_i=s_j\),则继承中间的 \(dp_{i+1,j-1}\) 即可。
在这种情况下,若区间长度 \(i \le 2\),则直接令 \(dp_{i,j}=0\) 即可。
code
#include<bits/stdc++.h>
using namespace std;
const int N=2e3+5;
int n,m;
string t;
int v[N];
int dp[N][N];
int main(){
cin>>n>>m>>t,t='#'+t;
for(int i=1;i<=n;i++){
char c; int x,y;
cin>>c>>x>>y;
v[c]=min(x,y);
}
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=m;i++) dp[i][i]=0;
for(int i=2;i<=m;i++){
for(int j=1;j+i-1<=m;j++){
int s=j,e=j+i-1;
if(t[s]==t[e]){
if(i==2) dp[s][e]=0;
else dp[s][e]=dp[s+1][e-1];
}
dp[s][e]=min(dp[s][e],min(dp[s][e-1]+v[t[e]],dp[s+1][e]+v[t[s]]));
}
}
cout<<dp[1][m];
return 0;
}
T3
与上题类似,此处略。
T4
一眼区间 dp。
令 \(dp_{i,j}\) 表示 \([i,j]\) 能折叠出的最小长度。
答案即为 \(dp_{1,n}\)(\(n\) 表示 \(S\) 的长度)。
初始状态显然有 \(dp_{i,i}=1\),其余为 \(\infty\)。
对于转移,我们进行分类讨论:
- 若不折叠,直接拼接,则按照枚举中转点套路即可。有转移:
-
否则若进行折叠,则重复部分的长度必为当前区间长度 \(len\) 的因数。
于是我们从 \(1\) 至 \(\frac{len}{2}\)(因为枚举到 \(len\) 就相当于情况 1)去枚举 \(len\) 的因数 \(l\),
并检查其是否满足要求。若满足,则有转移:
(其中 \(c(\frac{len}{l})\) 表示 \(\frac{len}{l}\) 的位数,即题中的 \(X\);\(2\) 表示两个括弧)
直接转移即可。
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e2+5;
int m;
string t;
int v[N];
int dp[N][N];
bool check(int l,int r,int k){
string o="";
for(int i=l;i<=r;i+=k){
if(i==l) o=t.substr(i,k);
else
if(t.substr(i,k)!=o) return 0;
}
return 1;
}
int c(int x){
int p=0;
while(x) p++,x/=10;
return p;
}
int main(){
cin>>t,t='#'+t,m=t.size()-1;
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=m;i++) dp[i][i]=1;
for(int i=2;i<=m;i++){
for(int j=1;j+i-1<=m;j++){
int s=j,e=j+i-1;
for(int k=s;k<e;k++) dp[s][e]=min(dp[s][e],dp[s][k]+dp[k+1][e]);
for(int l=1;l<=i/2;l++)
if(!(i%l)&&check(s,e,l))
dp[s][e]=min(dp[s][e],dp[s][s+l-1]+c(i/l)+2);
}
}
cout<<dp[1][m];
return 0;
}
作业 T1
令 \(dp_{i,j}\) 表示删去区间 \([i,j]\) 能获得的最大价值,答案即为 \(dp_{1,n}\)。
显然有初始状态令 \(dp_{i,i}=x_i\),其余为 \(-\infty\)。
对于 \([i,j]\),我们可以直接全删,于是先令 \(dp_{i,j}=\lvert x_i-x_j \rvert \times (j-i+1)\)。
然后按枚举中转点套路转移即可。
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e2+5;
int n,a[N],dp[N][N];
int main(){
cin>>n,memset(dp,0xcf,sizeof(dp));
for(int i=1;i<=n;i++) cin>>a[i],dp[i][i]=a[i];
for(int i=2;i<=n;i++){
for(int j=1;j+i-1<=n;j++){
int s=j,e=j+i-1;
dp[s][e]=abs(a[s]-a[e])*(e-s+1);
for(int k=s;k<e;k++)
dp[s][e]=max(dp[s][e],dp[s][k]+dp[k+1][e]);
}
}
cout<<dp[1][n];
return 0;
}
作业 T2
因为题中只含有两端点操作,所以考虑对端点进行转移。
具体地:
令 \(dp_{i,j,0}\) 表示区间 \([i,j]\) 中 \(i\) 从左边进的的初始队列方案数;
令 \(dp_{i,j,0}\) 表示区间 \([i,j]\) 中 \(j\) 从右边进的的初始队列方案数。
答案即为 \((dp_{1,n,0}+dp_{1,n,1}) \bmod 19650827\)。
然后显然有初始状态令 \(dp_{i,i,0}=1\),
这里默认第一个人从左进,避免重复计算。
接着考虑到第 \(i\) 人从左进时,它前面的人只可能为 \(i+1\) 或 \(j\),
因此它必须满足 \(h_i<h_{i+1}\) 或 \(h_i<h_j\) 才能进行转移,
易得此时转移方程:
同理,易得第 \(j\) 人从右进时的转移方程:
然后直接转移即可。注意取模。
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+5,MOD=19650827;
int n,a[N],dp[N][N][2];
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i],dp[i][i][0]=1;
for(int i=2;i<=n;i++){
for(int j=1;j+i-1<=n;j++){
int s=j,e=j+i-1;
if(a[s]<a[s+1]) dp[s][e][0]+=dp[s+1][e][0];
if(a[s]<a[e]) dp[s][e][0]+=dp[s+1][e][1];
if(a[e]>a[e-1]) dp[s][e][1]+=dp[s][e-1][1];
if(a[e]>a[s]) dp[s][e][1]+=dp[s][e-1][0];
dp[s][e][0]%=MOD,dp[s][e][1]%=MOD;
}
}
cout<<(dp[1][n][0]+dp[1][n][1])%MOD;
return 0;
}