区间dp
区间 通常解决的问题顺序有着很重要的作用,也就是说“前 个”的状态并不能很好地刻画出完整的状态
状态一般设计为 ,表示 完 的区间的花费
后面经常补充状态,比如 , 来表示上一次取的是左/右端点,左右端点的状态,或者区间后面的一段情况,或者区间内数具有的性质
从中间转移
即每次扩展的是一个区间,用于解决合并类的问题
最典型的有石子合并
再补充一些题目:
发现同样是一个合并类问题,那么
表示左右端点颜色分别为 的方案数
可以看左右端点是否是匹配括号,是则 ,否则 和
这道题的一个启发点是对于这种括号匹配的问题,往往写成递归版的记忆化搜索会更好实现
,表示 三个点形成了三角形并产生贡献
并不想讲这个题,来补充一个 道听途说未曾实现不保证正确 的小
这种括号匹配经常有算重的问题,那么对于每一个 把 从小到大枚举据说即可解决这个问题(理论上分析比较有道理)
被 教了一晚上
关键条件是转化关系是 对多的,那么设 表示 串区间 转化为字母 的最优方案
那么对于多步转移,由于越来越少( 对 需要特判),不会转移成环
实际上就是枚举局部最大值的位置,可以发现这个是不能计数的,应该放在状态里
形式化地,为经过 位置的所有区间最大值均在
很显然有多个,那么钦定为第一个
那么转移从 和 转移
其中左边一个区间可以发现需要迎合 是最大值这个限制
那么需要保证 和 管辖区间无交,这个可以对每个 预处理出来
注意右边是没有这个限制的,因为定义了是最小的一个,而这个最大值并不唯一
发现左边的转移是一个区间,那么维护一个前缀和即可
从两侧转移
POJ 3280 Cheapest Palindrome
题意:给你长度为m的字符串,其中有n种字符,每种字符都有两个值,分别是插入这个字符的代价,删除这个字符的代价,让你求将原先给出的那串字符变成一个回文串的最小代价。
这道题的思想还是很妙的
分情况讨论:
- 如果 相等,从 转移
- 在前面插入 或后面删除 ,从 转移
- 在后面插入 或前面删除 ,从 转移
很妙的题,当时不以为然,AT 又出来发现不会
分两个数组 分别维护小明和小华取完后的状态
那么
的转移同理
古老的题了,设 表示区间 ,最后一次老王在左/右端点的最优值
维护后缀信息
设 表示区间 ,后面紧接着有 个与 相同的方块
这样的状态使得一种转移迎刃而解:即选取中间一个点 ,其中 与 颜色相同,砍掉 的部分,让 以及后面的 个与 拼上
那么转移有两个:
- 这启示我们把转移不了的东西放进状态里
代码
#include<bits/stdc++.h>
using namespace std;
int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')f=-1;
ch=getchar();
}
while(isdigit(ch)){
x=x*10+ch-48;
ch=getchar();
}
return x*f;
}
const int maxn=205;
const int inf=0x3f3f3f3f;
int n,col[maxn],a[maxn],f[maxn][maxn][maxn*2],sum[maxn];
int main(){
n=read();
for(int i=1;i<=n;i++)col[i]=read();
for(int i=1;i<=n;i++)a[i]=read(),sum[i]=sum[i-1]+a[i];
// for(int i=1;i<=n;i++)f[i][i][0]=a[i]*a[i];
for(int len=0;len<=n-1;len++){
for(int l=1;l<=n-len;l++){
int r=l+len;
for(int s=0;s<=sum[n]-sum[r-1];s++){
f[l][r][s]=f[l][r-1][0]+(s+a[r])*(s+a[r]);
for(int k=l;k<=r-1;k++){
if(col[k]==col[r])
f[l][r][s]=max(f[l][r][s],f[l][k][a[r]+s]+f[k+1][r-1][0]);
}
}
}
}
cout<<f[1][n][0];
return 0;
}
发现既然一次分发只和最值有关,那么只记录区间最值即可
设 表示 中最值分别为 和 的最优情况
设 表示区间 的最优解
那么
考虑枚举断点 来转移
于是离散化后用 的优秀复杂度完成了此题
代码
#include<bits/stdc++.h>
using namespace std;
int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')f=-1;
ch=getchar();
}
while(isdigit(ch)){
x=x*10+ch-48;
ch=getchar();
}
return x*f;
}
const int maxn=55;
int n,m,aa,b,a[maxn],lsh[maxn],tot,f[maxn][maxn],g[maxn][maxn][maxn][maxn];
void placemin(int &a,int b){
if(a>b)a=b;
return ;
}
int main(){
n=read(),aa=read(),b=read();
for(int i=1;i<=n;i++)lsh[i]=a[i]=read();
sort(lsh+1,lsh+n+1);
tot=unique(lsh+1,lsh+n+1)-lsh-1;
for(int i=1;i<=n;i++)a[i]=lower_bound(lsh+1,lsh+tot+1,a[i])-lsh;
memset(g,0x3f,sizeof g),memset(f,0x3f,sizeof f);
for(int i=1;i<=n;i++)g[i][i][a[i]][a[i]]=0;
for(int len=0;len<=n-1;len++){
for(int l=1;l<=n-len;l++){
int r=l+len;
for(int mn=1;mn<=tot;mn++){
for(int mx=mn;mx<=tot;mx++){
for(int k=l;k<r;k++){
placemin(g[l][r][mn][mx],g[l][k][mn][mx]+f[k+1][r]);
}
placemin(g[l][r+1][min(mn,a[r+1])][max(mx,a[r+1])],g[l][r][mn][mx]);
placemin(f[l][r],g[l][r][mn][mx]+aa+b*(lsh[mx]-lsh[mn])*(lsh[mx]-lsh[mn]));
}
}
}
}
cout<<f[1][n];
return 0;
}
这是区间 放到二维平面上的例子
设 表示顶点为 与 的矩形分割了 次的最小值
每次只扩展横纵坐标之一
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=16;
int n,a[maxn][maxn],sum[maxn][maxn],f[maxn][maxn][maxn][maxn][maxn];
void tomin(int &a,int b){
if(a>b)a=b;
return ;
}
int main(){
cin>>n;
for(int i=1;i<=8;i++){
for(int j=1;j<=8;j++){
cin>>a[i][j];
sum[i][j]=sum[i-1][j]+sum[i][j-1]+a[i][j]-sum[i-1][j-1];
}
}
memset(f,0x3f,sizeof f);
for(int i=1;i<=8;i++){
for(int j=1;j<=8;j++){
for(int l=i;l<=8;l++){
for(int r=j;r<=8;r++){
int w=sum[l][r]-sum[i-1][r]-sum[l][j-1]+sum[i-1][j-1];
f[i][j][l][r][0]=w*w;
}
}
}
}
for(int k=1;k<=n;k++){
for(int i=1;i<=8;i++){
for(int j=1;j<=8;j++){
for(int l=1;l<=8;l++){
for(int r=1;r<=8;r++){
for(int a=i;a<l;a++){
tomin(f[i][j][l][r][k],min(f[i][j][a][r][0]+f[a+1][j][l][r][k-1],f[i][j][a][r][k-1]+f[a+1][j][l][r][0]));
}
for(int b=j;b<r;b++){
tomin(f[i][j][l][r][k],min(f[i][j][l][b][0]+f[i][b+1][l][r][k-1],f[i][j][l][b][k-1]+f[i][b+1][l][r][0]));
}
}
}
}
}
}
cout<<f[1][1][8][8][n-1];
return 0;
}
根据题目的限制,不能更改数据值,也就是说这棵 的中序遍历是一定了,要做的是通过改变权值来旋转
设 表示中序 的节点,值 的最小值
那么枚举一个树根 ,转移分情况讨论:
如果 的值大于等于 ,那么
否则,
代码
#include<bits/stdc++.h>
using namespace std;
int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')f=-1;
ch=getchar();
}
while(isdigit(ch)){
x=x*10+ch-48;
ch=getchar();
}
return x*f;
}
const int maxn=75;
const int inf=1e9;
int n,m,f[maxn][maxn][maxn],sum[maxn],lsh[maxn];
struct Node{
int val,val1,cost;
}a[maxn];
bool cmp(Node a,Node b){
return a.val<b.val;
}
int main(){
n=read();m=read();
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)for(int k=1;k<=n;k++)if(j!=i-1)f[i][j][k]=inf;
for(int i=1;i<=n;i++)a[i].val=read();
for(int i=1;i<=n;i++)lsh[i]=a[i].val1=read();
for(int i=1;i<=n;i++)a[i].cost=read();
sort(a+1,a+n+1,cmp);
sort(lsh+1,lsh+n+1);
for(int i=1;i<=n;i++)a[i].val1=lower_bound(lsh+1,lsh+n+1,a[i].val1)-lsh;
for(int i=1;i<=n;i++)sum[i]=sum[i-1]+a[i].cost;
// for(int i=1;i<=n;i++){
// for(int j=a[i].val1;j<=n;j++)f[i][i][j]=a[i].cost;
// for(int j=1;j<a[i].val1;j++)f[i][i][j]=a[i].cost+m;
// }
for(int len=0;len<=n-1;len++){
for(int l=1;l<=n-len;l++){
int r=l+len;
for(int s=1;s<=n;s++){
for(int k=l;k<=r;k++){
if(a[k].val1>=s)f[l][r][s]=min(f[l][r][s],f[l][k-1][a[k].val1]+f[k+1][r][a[k].val1]+sum[r]-sum[l-1]);
f[l][r][s]=min(f[l][r][s],f[l][k-1][s]+f[k+1][r][s]+sum[r]-sum[l-1]+m);
}
}
}
}
cout<<f[1][n][1];
return 0;
}
题意:每次可以删除串中的一个 ,最少剩下多长
其意义在于这种消除的模式并不能贪心,其具有区间合并性
而 状态相当于也是维护了后缀信息
详见 这里
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=205;
int t,n,lenans,nxt[maxn],cnt,sum[30],sum1[30];
bool f[maxn][maxn];
char a[maxn],b[maxn],c[maxn],ans[maxn];
void solve(int l,int r){
int len=r-l+1;
for(int i=l;i<=r;i++)b[i-l+1]=a[i];
memset(f,0,sizeof f);
for(int i=1;i<=n;i++){
f[i][i-1]=true;
}
for(int len1=0;len1<=n-1;len1++){
for(int l=1;l<=n-len1;l++){
int r=l+len1;
f[l][r]|=f[l][r-1]&(a[r]==b[len1%len+1]);
for(int k=1;k*len<=len1;k++){
f[l][r]|=f[l][r-k*len]&f[r-k*len+1][r];
if(f[l][r])break;
}
// cout<<l<<" "<<r<<" "<<f[l][r]<<endl;
}
}
if(f[1][n]){
lenans=len;
for(int i=1;i<=len;i++)ans[i]=b[i];
}
return ;
}
bool cmp(int len,int l,int r){
if(!len)return true;
for(int i=l;i<=r;i++){
if(a[i]!=ans[i-l+1])return a[i]<ans[i-l+1];
}
return false;
}
bool check(int l,int r){
memset(sum1,0,sizeof sum1);
int base=n/(r-l+1);
for(int i=l;i<=r;i++){
sum1[a[i]-'a']++;
}
// cout<<"hhh"<<endl;
for(int i=0;i<26;i++){
if(sum1[i]*base!=sum[i])return false;
}
return true;
}
int main(){
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
cin>>t;
while(t--){
memset(sum,0,sizeof sum);
scanf("%s",a+1);
n=strlen(a+1);lenans=0;
for(int i=1;i<=n;i++){
sum[a[i]-'a']++;
}
for(int len=0;len<=n-1;len++){
// cout<<n<<" "<<len+1<<endl;
if(n%(len+1))continue;
// cout<"hhh";
for(int l=1;l<=n-len;l++){
int r=l+len;
if(cmp(lenans,l,r)){
// cout<<"hhh";
if(check(l,r))solve(l,r);
}
}
if(lenans)break;
}
for(int i=1;i<=lenans;i++){
printf("%c",ans[i]);
}
puts("");
}
return 0;
}
- 资料
- 咕咕咕
区间 与四边形不等式连接紧密,继续往后咕
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
2021-08-05 noip模拟30