算法提高课 第一章 动态规划 区间DP


282. 石子合并

#include
#include
#include

using namespace std;
const int N = 305,INF = 0x3f3f3f3f;
int f[N][N],a[N],s[N];
int n;

int main()
{
    cin>>n;
    for(int i = 1;i <= n;i++)
    {
        cin>>a[i];
        s[i] = s[i-1] + a[i];
    }
    
    for(int len = 2;len <= n;len++) //区间长度为1时,不需要合并,代价为0,故长度从2开始枚举
    {
        for(int i = 1;i + len - 1 <= n;i++)
        {
            int l = i, r = i + len - 1;//左右边界
            f[l][r] = INF;
            for(int k = l;k < r;k++) //分界点 [l,r-1]
            {
                f[l][r] = min(f[l][r],f[l][k] + f[k+1][r] + s[r] - s[l-1]);
            }
        }
    }
    cout<

1068. 环形石子合并


#include 
#include 
#include 

using namespace std;

const int N = 410,INF = 0x3f3f3f3f;

int a[N],n;
int f[N][N];//f[l][r]:考虑长度不超过n的区间[l,r]中两两合并的所有方案中,得分总和最大值
int g[N][N];//f[l][r]:考虑长度不超过n的区间[l,r]中两两合并的所有方案中,得分总和最小值
int s[N];//前缀和

int main()
{
    scanf("%d", &n);
    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];
    }
    memset(f,-INF,sizeof f);
    memset(g,INF,sizeof g);
    for(int len = 1;len<=n;len++) //枚举区间长度
    {
        for(int i = 1;i+len-1<=2*n;i++)//枚举区间起点
        {
            int l = i,r = i+len-1;//算出左右端点
            if(len == 1)//注意:若长度为1,无需合并,特殊处理
            {
                f[l][r] = g[l][r] = 0;
                continue;
            }
            for(int k = l;k

320. 能量项链


直接当成珠子来看待即可,模拟合并过程。
区间长度为n

#include 
#include 
#include 

using namespace std;

const int N = 210;

int a[N],n;
int f[N][N];//f[l][r]:区间[l,r]的所有合并方案中,能量的最大值

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ )
    {
        scanf("%d", &a[i]);
        a[i+n] = a[i];
    }
    memset(f,-0x3f,sizeof f);
    for(int len = 1;len<=n;len++)
    {
        for(int l = 1;l+len-1<=2*n;l++)
        {
            int r = l+len-1;
            if(len == 1)//长度为1,无需合并
            {
                f[l][r] = 0;
                continue;
            }
            for(int k = l;k 4 5 7 4
                //这里的过程同石子合并,这里不难想到若将l到k的珠子合并之后会变成一个首是l而尾k+1的珠子;
            //同理若将k+1到r的珠子合并之后会变成一个首是k+1而尾r+1的珠子;
                
            }
        }
    }
    int ans = -1;
    for (int i = 1; i <= n; i ++ )
    {
        ans = max(ans,f[i][i+n-1]);
    }
    cout<

1069. 凸多边形的划分

区间DP+高精度

#include 
#include 
#include 
#include 

using namespace std;
typedef long long LL;

const int N = 55;

int a[N],n;
vector f[N][N];

vectoradd(vector&a,vector&b) //高精度加法
{
    vectorc;
    LL t = 0;
    for(int i = 0;i&a,vector&b) //高精度比大小
{
    if(a.size()!=b.size()) return a.size() > b.size();
    for(int i = a.size()-1;i>=0;i--)
    {
        if(a[i] > b[i]) return 1;
        if(a[i] < b[i]) return -1;
    }
    return 0;
}
vectormul(vector&a,LL b) //高精度乘法
{
    vectorc;
    LL t = 0;
    for(int i = 0;i&a) //输出,注意略去前导0
{
    int i = a.size() - 1;
    while(i>=0 && a[i] == 0) --i;
    while(i>=0) 
    {
        cout<{0};
                continue;
            }
            f[l][r] = vector(N,9); //初始化为正无穷
            for(int k = l+1;kt;
                t.push_back(a[l]);
                t = mul(t,a[k]);
                t = mul(t,a[r]);
                t = add(t,f[l][k]);
                t = add(t,f[k][r]);
                if(cmp(f[l][r],t)>0) f[l][r] = t;
                //f[l][r] = min(f[l][r],f[l][k] + f[k][r] + a[l]*a[k]*a[r]);
            }
        }
    }
    
    print(f[1][n]);
    return 0;  
}

479. 加分二叉树


用root[L,R]记录划分方案(根结点)

#include 
#include 
#include 

using namespace std;

const int N = 35;

int a[N],n;
int f[N][N];
int root[N][N];

void dfs(int l,int r) //前序遍历
{
    if(l>r) return;
    int t = root[l][r];
    cout< f[l][r]) //题目要求字典序最小,因此大于再更新
                {
                    f[l][r] = score;
                    root[l][r] = k;
                }
                //f[l][r] = max(f[l][r],f[l][k]*f[k+1][r] + a[k]);
            }
        }
    }
    cout<

321. 棋盘分割

#include 
#include 
#include 
#include 

using namespace std;

const int N = 10,K = 16,INF = 0x3f3f3f3f;

double f[N][N][N][N][K];//f[x1][y1][x2][y2][k]:(x1,y1)到(x2,y2)区域分成k块的所有方案中,方差和最小的
int s[N][N];//二维前缀和
int n;
double X;

double get(int x1,int y1,int x2,int y2) //计算出(x1,y1)到(x2,y2)区域得分的方差,前缀和O(1)算区域和
{
    double sum = s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1];
    sum -= X;
    return 1.0*sum * sum;
}
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 == n) return v = get(x1,y1,x2,y2);//注意:分为n块时,仍需要算出矩形区域内的方差
    
    double res = INF;
    for(int t = x1;t