区间DP及其拓展

石子合并

282. 石子合并 - AcWing题库https://www.acwing.com/problem/content/284/

环形石子合并 

题目 环形石子合并 

#include<bits/stdc++.h>
	using namespace std;
	const int N=410,INF=0x3f3f3f3f;
	int f[N][N];
	int g[N][N];
	int a[N];
	int s[N];
	int main()
	{
		int n;
		cin>>n;
		memset(f,INF,sizeof f);
		memset(g,-INF,sizeof g);
		for(int i=1;i<=n;i++)
		{
			cin>>a[i];
			a[i+n]=a[i];
		}
		for(int i=1;i<=n*2;i++)
		{
			s[i]=a[i]+s[i-1];
			f[i][i]=0;
			g[i][i]=0;
		}
		
		for(int len=2;len<=n;len++)
		for(int i=1;i+len-1<=n*2;i++)
		{
			int l=i,r=len+i-1;
			
			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]);
				g[l][r]=max(g[l][r],g[l][k]+g[k+1][r]+s[r]-s[l-1]);
			}
		}
		int minv=INF,maxv=-INF;
		for(int i=1;i<=n;i++)
		{
			minv=min(minv,f[i][i+n-1]);
			maxv=max(maxv,g[i][i+n-1]);
		}
		cout<<minv<<endl<<maxv<<endl;
	} 

能量项链【区间DP】 

 题目 能量项链https://www.acwing.com/problem/content/description/322/

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=210;
ll f[N][N];
ll a[N];
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        a[i+n]=a[i];
    }
    for(int len=3;len<=n+1;len++)
    for(int l=1;len+l-1<=2*n;l++)
    {
        int r=l+len-1;
        for(int k=l+1;k<r;k++)
        f[l][r]=max(f[l][r],f[l][k]+f[k][r]+a[l]*a[k]*a[r]);
    }
    
    ll res=0;
    for(int i=1;i<=n;i++)
    res=max(res,f[i][i+n]);
    cout<<res<<endl;
}

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=35;
int w[N],f[N][N],g[N][N];//f记录[l,r]区间的分数,g记录[l,r]的根节点
void dfs(int l,int r)//前序遍历是根左右
{
    if(l>r) return ;
    int root=g[l][r];//根
    cout<<root<<' ';
    dfs(l,root-1);//左子树
    dfs(root+1,r);//右子树
}
int main()
{
   int n;
   cin>>n;
   for(int i=1;i<=n;i++)  cin>>w[i];
   memset(f,-0x3f,sizeof f);   //因为要求最大值,则初始化为负无穷
   for(int len=1;len<=n;len++)  //枚举[l,r]的长度
     for(int l=1;l+len-1<=n;l++)//枚举l
     {
         int r=l+len-1;
         if(len==1)  //假如只有自己,则分数是自己,根也是自己
        {
            f[l][r]=w[l];
            g[l][r]=l;
        }
         else
         {
             for(int k=l;k<=r;k++)//枚举[l,r]之间的根节点
             {
                 int left = k==l ? 1 : f[l][k-1];//假如k是等于l,说明l到k没有左子树,则分数为1
                 int right = k==r ? 1 : f[k+1][r];//假如k是等于r,说明l到k没有右子树,则分数为1
                 int socre=left*right+w[k];//算一下分数
                 if(f[l][r]<score)//假如可以更新最大值
                {
                    f[l][r]=score;
                    g[l][r]=k;
                }//更新分数,还有[l,r]的根节点k 
             }
         }
     }
    cout<<f[1][n]<<endl;//输出[l,r]区间的二叉树的分数
    dfs(1,n);//前序遍历求根节点
    return 0;
}

 凸多边形的划分【区间DP+高精度=缝合怪】 

 由于这题每个w[i]都是10^9,则三个乘以的话就10^27,则要自己写个高精度加法与乘法

//没加高精度的模板,过不了
#include<bits/stdc++.h>
using namespace std;
const int N=210;
int w[N],f[N][N];
int main()
{
  int n;
  cin>>n;
  for(int i=1;i<=n;i++)  cin>>w[i];
  for(int len=3;len<=n;len++)//枚举l到r的长度
    for(int l=1;l+len-1<=n;l++)
    {
        int r=len+l-1;
        f[l][r]=0x3f3f3f3f;//因为要求最小值,所以初始化为正无穷
        for(int k=l+1;k<r;k++)
            f[l][r]=min(f[l][r],f[l][k]+f[k][r]+w[l]*w[k]*w[r]);
    }
      cout<<f[1][n]<<endl;
    return 0;
}

高精度 优化

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=55,M=35;
ll w[N],f[N][N][M];
void add(ll a[],ll b[])//高精度加法
{
    static ll c[M];
    memset(c,0,sizeof c);
    ll t=0;
    for(int i=0;i<M;i++)
    {
        t+=a[i]+b[i];
        c[i]=t%10;
        t/=10;
    }
    memcpy(a,c,sizeof c);
}
void mul(ll a[],ll b)//高精度乘法
{
    static ll c[M];
    memset(c,0,sizeof c);
    ll t=0;
    for(int i=0;i<M;i++)
    {
        t+=a[i]*b;
        c[i]=t%10;
        t/=10;
    }
    memcpy(a,c,sizeof c);
}
bool cmp(ll a[],ll b[])//比较两个数的大小
{
    for(int i=M-1;i>=0;i--)
        if(a[i]>b[i]) return true;
        else if(a[i]<b[i]) return false;
    return false;
}
void print(ll a[])//用来输出答案
{
    int k=M-1;
    while(k&&!a[k]) k--;
    while(k>=0) cout<<a[k--];
    cout<<endl;
}
int main()
{
  int n;
  cin>>n;
  for(int i=1;i<=n;i++)  cin>>w[i];
  ll temp[M];
  for(int len=3;len<=n;len++)//枚举l到r的长度
    for(int l=1;l+len-1<=n;l++)
    {
        int r=len+l-1;
        //f[l][r]=0x3f3f3f3f;
        f[l][r][M-1]=1;//因为要求最小值,所以初始化为正无穷,最高位是1,说明1e34,已经很大了
        for(int k=l+1;k<r;k++)
        {
        // f[l][r]=min(f[l][r],f[l][k]+f[k][r]+w[l]*w[k]*w[r]);
            memset(temp,0,sizeof temp);//清空上一次的数组
            temp[0]=w[l];
            mul(temp,w[k]);
            mul(temp,w[r]);
            add(temp,f[l][k]);
            add(temp,f[k][r]);
            if(cmp(f[l][r],temp))
                memcpy(f[l][r],temp,sizeof temp);
        }
    }
     print(f[1][n]);
    return 0;
}

加分二叉树 

题目 icon-default.png?t=N3I4http://ybt.ssoier.cn:8088/problem_show.php?pid=1833

 

 

#include<bits/stdc++.h>
using namespace std;
const int N=35;
int f[N][N],g[N][N];
int w[N];
int n;
void dfs(int l,int r)
{
	if(l>r)return;
	int root=g[l][r];
	cout<<root<<" ";
	dfs(l,root-1);
	dfs(root+1,r); 
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	cin>>w[i];
	for(int len=1;len<=n;len++)
	for(int l=1;l+len-1<=n;l++)
	{
		int r=l+len-1;
		if(len==1)
		{
			f[l][r]=w[l];
			g[l][r]=l;	
		}
		else
		{
			for(int k=l;k<=r;k++)
			{
				int left = k==l ? 1 : f[l][k-1];
				int right = k==r ? 1 :f[k+1][r];
				int sorce = w[k] + left * right;
				if(f[l][r]<sorce)
				{
					f[l][r]=sorce;
					g[l][r]=k;
				}
			}
		}
	}
	cout<<f[1][n]<<endl;
	dfs(1,n);
} 

 

棋盘分割 

题目 棋盘分割https://www.acwing.com/problem/content/description/323/

解析 https://www.acwing.com/solution/content/62836/

横着切: 

 竖着切:

#include<bits/stdc++.h>
using namespace std;
const int N=15,M=9;
const double INF=1e9;
int s[M][M];//二维前缀和
double f[M][M][M][M][N];
double X;//平均数
int n;
double get(int x1,int y1,int x2,int y2)//求子矩阵的方差
{
    double sum=s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]-X;//求方差
    return sum*sum/n;
}
double dp(int x1,int y1,int x2,int y2,int k)//记忆化搜索
{
    double &v=f[x1][y1][x2][y2][k];
    if(v>=0) return v;//假如已经处理过直接返回他的值
    if(k==1) return v=get(x1,y1,x2,y2);//假如切割是一次,则就是最后一块子矩阵,则直接求他的值,不用再切割
    v=INF;//因为要求最小值,初始化为正无穷
    for(int i=x1;i<x2;i++)//枚举横向切割
    {
        v=min(v,dp(x1,y1,i,y2,k-1)+get(i+1,y1,x2,y2));//取上面的部分
        v=min(v,dp(i+1,y1,x2,y2,k-1)+get(x1,y1,i,y2));//取下面的部分
    }
    for(int i=y1;i<y2;i++)//枚举纵向切割
    {
        v=min(v,dp(x1,y1,x2,i,k-1)+get(x1,i+1,x2,y2));//取左边部分
        v=min(v,dp(x1,i+1,x2,y2,k-1)+get(x1,y1,x2,i));//取右边部分
    }
    return v;
 
}
int main()
{
   cin>>n;
   for(int i=1;i<=8;i++)
     for(int j=1;j<=8;j++)
     {
        cin>>s[i][j];
        s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];//二维前缀和
     }
    memset(f,-1,sizeof f);//初始化为-1,便于记忆化搜索
    X=(double)s[8][8]/n;//求平均数
    printf("%.3lf\n",sqrt(dp(1,1,8,8,n)));//输出根号方差,即均方差
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值