E28【模板】区间DP 石子合并

E28【模板】区间DP 石子合并——信息学竞赛算法_哔哩哔哩_bilibili

 

P1775 石子合并(弱化版) - 洛谷

[1,1] [1,2] [1,3] [1,4] [1,5]
        [2,2] [2,3] [2,4] [2,5]
                [3,3] [3,4] [3,5]
                        [4,4] [4,5]
                                [5,5]

对角线初值,上三角无穷,上三角转移

// 区间DP O(n^3)
#include<bits/stdc++.h>
using namespace std;

const int N=310;
int n,a[N],s[N];
int f[N][N]; //f[i][j]表示把从i到j合并的最小代价 

int main(){
  cin>>n;
  for(int i=1;i<=n;i++) cin>>a[i],s[i]=s[i-1]+a[i];
  memset(f,0x3f,sizeof(f));
  for(int i=1;i<=n;i++) f[i][i]=0;
  
  for(int len=2; len<=n; len++)     //枚举区间长度
  for(int l=1,r=len; r<=n; l++,r++) //枚举区间端点
  for(int k=l; k<r; k++)            //枚举区间断点
    f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
  
  cout<<f[1][n];
}

 

P2308 添加括号 - 洛谷

// 区间DP O(n^3)
#include<bits/stdc++.h>
using namespace std;

const int N=205;
int n,a[N],s[N];
int f[N][N],p[N][N];

void path(int l,int r){
  if(l==r){cout<<a[l]; return;}
  cout<<"(";
  path(l,p[l][r]);
  cout<<"+";
  path(p[l][r]+1,r);
  cout<<")";
}
void dfs(int l,int r){
  if(l==r) return;
  dfs(l,p[l][r]);
  dfs(p[l][r]+1,r);
  cout<<s[r]-s[l-1]<<" ";
}
signed  main(){
  cin>>n;
  for(int i=1;i<=n;i++) cin>>a[i],s[i]=s[i-1]+a[i];
  
  memset(f,0x3f,sizeof(f));
  for(int i=1;i<=n;i++) f[i][i]=0;
  
  for(int len=2;len<=n;len++)
  for(int l=1,r=len;r<=n;l++,r++)
  for(int k=l;k<r;k++)
    if(f[l][r]>f[l][k]+f[k+1][r]+s[r]-s[l-1])
      f[l][r]=f[l][k]+f[k+1][r]+s[r]-s[l-1],p[l][r]=k;

  path(1,n);
  cout<<endl<<f[1][n]<<endl;
  dfs(1,n);
}

 

P1388 算式 - 洛谷

// 区间DP O(n^5)
#include<bits/stdc++.h>
using namespace std;

#define N 20
int n,m,a[N],s[N];
int f[N][N][N]; //f[l,r,t]表示区间[l,r]插入t个*,合并后的最大值

int main(){
  scanf("%d%d",&n,&m);
  for(int i=1;i<=n;i++)scanf("%d",a+i),s[i]=s[i-1]+a[i];
  for(int i=1;i<=n;i++)
  for(int j=i;j<=n;j++) f[i][j][0]=s[j]-s[i-1];

  for(int len=2;len<=n;len++)
  for(int l=1,r=len;r<=n;l++,r++)
  for(int k=l;k<r;k++)
  for(int t=1;t<=m;t++)
  for(int p=0;p<=min(k-l,t);p++){
    f[l][r][t]=max(f[l][r][t],f[l][k][p]*f[k+1][r][t-p-1]);
    if(t-p<r-k) //l~k~r,p=[0,k-l],t-p<r-k
    f[l][r][t]=max(f[l][r][t],f[l][k][p]+f[k+1][r][t-p]);
  }

  printf("%d\n",f[1][n][m]);
  return 0;
}

 

P3146 [USACO16OPEN] 248 G - 洛谷

// 区间DP O(n^3)
#include<bits/stdc++.h>
using namespace std;

const int N=250;
int n,ans,f[N][N]; //f[i,j]表示把从i到j合并获得的最大值

int main(){
  scanf("%d",&n);
  for(int i=1; i<=n; i++){
    scanf("%d",&f[i][i]);
    ans=max(ans,f[i][i]);
  }
  
  for(int len=2; len<=n; len++){
    for(int i=1,j=len; j<=n; i++,j++){
      for(int k=i; k<j; k++){
        if(f[i][k]==f[k+1][j] && f[i][k]){ //两区间f均为0 不转移,例[1 2][3 4]
          f[i][j]=max(f[i][j],f[i][k]+1);
          ans=max(ans,f[i][j]);
        }
      }
    }
  }
  printf("%d\n",ans);        
  return 0;
} 

 

P4805 [CCC 2016] 合并饭团 - 洛谷

// 区间DP+双指针 O(n^3)
#include<bits/stdc++.h>
using namespace std;

int n,f[405][405],ans;

int main(){
  cin>>n;
  for(int i=1; i<=n; ++i){
    cin>>f[i][i];
    ans=max(ans,f[i][i]);
  }
  for(int len=2; len<=n; ++len){
    for(int i=1,j=len; j<=n; ++i,++j){
      for(int k=i; k<j; ++k){ //合并两个
        if(f[i][k] && f[i][k]==f[k+1][j]){
          f[i][j]=f[i][k]+f[k+1][j];
          break;
        }
      }
      for(int k=i,t=j; k<t-1; ){ //合并三个
        if(f[i][j]) break;
        if(!f[i][k]) ++k;
        else if(!f[t][j]) --t;
        else if(f[i][k]==f[t][j]){
          if(f[k+1][t-1]) f[i][j]=f[i][k]+f[k+1][t-1]+f[t][j];
          else ++k,--t;
        }
        else if(f[i][k]<f[t][j]) ++k;
        else if(f[i][k]>f[t][j]) --t;
      }
      ans=max(ans,f[i][j]);
    }
  }
  cout<<ans;
}

 

P2858 [USACO06FEB] Treats for the Cows G/S - 洛谷

满足最优子区间,所以区间DP,每次的断点只有两个

// 区间DP O(n^2)
#include<bits/stdc++.h>
using namespace std;

const int N=2005;
int n,a[N],f[N][N];

int main(){
  cin>>n;
  for(int i=1;i<=n;i++) cin>>a[i];
  
  for(int len=1; len<=n; len++){
    for(int i=1,j=len; j<=n; i++,j++){
      f[i][j]=max(f[i+1][j]+a[i]*(n-len+1),f[i][j-1]+a[j]*(n-len+1));
    } //len=1时最后一天卖 a*n,len=2时倒数第二天卖 a*(n-1)...
  }
  cout<<f[1][n];
}

 

P1005 [NOIP 2007 提高组] 矩阵取数游戏 - 洛谷

行间独立,每行满足最优子区间,所以区间DP,每次的断点只有两个

// 区间DP O(1000*n^2)
#include <bits/stdc++.h>
using namespace std;

const int N=82;
int n,m;
__int128 a[N][N],f[N][N],ans; //f[i,j]表示合并区间[i,j]获得的最大值

void input(__int128 &s){
  s=0; char c;
  while(c>'9'||c<'0') c=getchar();
  while(c>='0'&&c<='9'){
    s=s*10+c-'0';
    c=getchar();
  }
}
void output(__int128 x){
  if(x>9) output(x/10);
  putchar(x%10+'0');
}
__int128 work(__int128 a[]){
  memset(f,0,sizeof(f));
  for(int len=1; len<=m; ++len)
  for(int i=1,j=len; j<=m; ++i,++j)
    f[i][j]=max(f[i+1][j]+a[i]*((__int128)1<<m-len+1),f[i][j-1]+a[j]*((__int128)1<<m-len+1));
    // f[i][j]=max(a[i]+f[i+1][j],f[i][j-1]+a[j])*2; //类似秦九韶
  return f[1][m];
}
int main(){
  cin>>n>>m;
  for(int i=1;i<=n;i++)
  for(int j=1;j<=m;j++) input(a[i][j]);
  
  for(int i=1;i<=n;i++) ans+=work(a[i]);
  output(ans);
}

 

P1435 [IOI 2000] 回文字串 - 洛谷

// 区间DP O(n^2)
#include<bits/stdc++.h>
using namespace std;

const int N=1005;
char a[N];
int n,f[N][N]; //f[i,j]把从i到j的字符串变为回文串的最少插入字符数

int main(){
  scanf("%s",a+1);
  n=strlen(a+1);

  for(int len=2; len<=n; len++){
    for(int i=1,j=len; j<=n; i++,j++){
      if(a[i]==a[j]) f[i][j]=f[i+1][j-1];
      else f[i][j]=min(f[i+1][j],f[i][j-1])+1;
    }
  }
  cout<<f[1][n];
}

 

P3847 [TJOI2007] 调整队形 - 洛谷

// 区间DP O(n^2)
#include<bits/stdc++.h>
using namespace std;

const int N=3005;
int n,a[N],f[N][N]; //f[i][j]表示从i到j对称的最小操作次数 

int main(){
  scanf("%d",&n);
  for(int i=1;i<=n;i++) scanf("%d",&a[i]);
  
  for(int len=2;len<=n;len++){
    for(int i=1,j=len;j<=n;i++,j++){
      if(a[i]==a[j]) f[i][j]=f[i+1][j-1];
      else f[i][j]=min(min(f[i+1][j],f[i][j-1]),f[i+1][j-1])+1;
    }
  }
  cout<<f[1][n];
}

 

P9325 [CCC 2023 S2] Symmetric Mountains - 洛谷

// 区间DP O(n^2)
#include<bits/stdc++.h>
using namespace std;

const int N=5005;
int n,a[N];
int f[N][N],ans[N];

int main(){
  cin>>n;
  for(int i=1;i<=n;i++) cin>>a[i];
  memset(ans,0x3f,sizeof ans); ans[1]=0;
  
  for(int len=2; len<=n; len++){
    for(int i=1,j=len; j<=n; i++,j++){
      f[i][j]=f[i+1][j-1]+abs(a[i]-a[j]);
      ans[len]=min(ans[len],f[i][j]);
    }
  }
  for(int i=1;i<=n;i++) cout<<ans[i]<<" ";
}

 

P6006 [USACO20JAN] Farmer John Solves 3SUM G - 洛谷

// 区间DP O(n^2)
#include<bits/stdc++.h>
using namespace std;

const int N=5010,M=1e6;
int n,q,a[N],cnt[3000010];
long long f[N][N];

int main(){
  ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
  cin>>n>>q;
  for(int i=1;i<=n;i++) cin>>a[i];
  
  for(int i=1; i<=n; i++){
    for(int j=i+1; j<=n; j++){
      f[i][j]=cnt[a[i]+a[j]+M]; //f[i][j]:a[i]+a[j]+a[i+1~j-1]=0 的个数
      cnt[-a[j]+M]++;           //cnt:[i+1~j]内值为-a[j]的个数
    }
    for(int j=i+1; j<=n; j++) cnt[-a[j]+M]=0;
  }
  
  for(int len=3; len<=n; len++){
    for(int i=1,j=len; j<=n; i++,j++){
      f[i][j]=f[i+1][j]+f[i][j-1]-f[i+1][j-1]+f[i][j]; //平面内点的个数容斥
    }
  }
  while(q--){
    int a,b; cin>>a>>b;
    cout<<f[a][b]<<"\n";
  }
}

 

P3102 [USACO14FEB] Secret Code S - 洛谷

// 区间DP O(n^3)
#include<bits/stdc++.h>
#define mod 2014
using namespace std;

const int N=105;
char s[N];
int n,d[N][N][N]; //d[i,j,k]表示[i,i+k−1]和[j,j+k−1]是否相等,k为长度
int f[N][N];      //f[i,j]表示区间[i,j]内的方案数

int main(){
  scanf("%s",s+1);
  n=strlen(s+1);
  for(int i=1; i<=n; i++)
  for(int j=i; j<=n; j++)
  for(int k=1; k+i-1<=n && k+j-1<=n; k++)
    if(s[i+k-1]==s[j+k-1]) d[i][j][k]=1;
    else break;
  
  for(int i=1; i<=n; i++)
  for(int j=1; j<=n; j++) f[i][j]=1;
  
  for(int len=3; len<=n; len++)
  for(int i=1,j=len; j<=n; i++,j++)
  for(int k=i; k<j; k++){
    int a=k-i+1, b=j-k;
    if(a<b && d[i][k+1][a]) f[i][j]+=f[k+1][j];   //a段=b前段
    if(a<b && d[i][j-a+1][a]) f[i][j]+=f[k+1][j]; //a段=b后段
    if(a>b && d[i][k+1][b]) f[i][j]+=f[i][k];     //a前段=b段
    if(a>b && d[k-b+1][k+1][b]) f[i][j]+=f[i][k]; //a后段=b段
    f[i][j]%=mod;
  }

  printf("%d",f[1][n]-1);
}

 

P11676 [USACO25JAN] DFS Order P - 洛谷

// 区间DP O(n^3)
#include<bits/stdc++.h>
using namespace std; 

const int N=755; 
int n,c,a[N][N],s[N][N];
int f[N][N]; //f[i,j]表示以i为根的子树包含区间[i,j]的最小代价

int main(){
  ios::sync_with_stdio(0),cin.tie(0); 
  cin>>n;
  for(int j=2; j<=n; j++)
  for(int i=1; i<j; i++)
    cin>>a[i][j],c+=a[i][j]<0?-a[i][j]:0; //c所有已有边权之和
    
  for(int i=1; i<=n; i++)
  for(int j=i+1; j<=n; j++)
    s[i][j]=s[i][j-1]+(a[i][j]<0?a[i][j]:0); //s[i,j]以i为根的负边权的前缀和
  
  memset(f,0x3f,sizeof f);
  for(int i=1; i<=n; i++) f[i][i]=0;
  
  for(int len=2; len<=n; len++)
  for(int i=1,j=len; j<=n; i++,j++)
  for(int k=i; k<j; k++){
    f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[i][j]-s[i][k+1]+a[i][k+1]);
    // printf("[%d %d]=%d\n",i,j,f[i][j]);
  }
  
  cout<<f[1][n]+c;
}

 

posted @ 2023-04-10 10:14  董晓  阅读(1027)  评论(0)    收藏  举报