4.5算法之动态规划 193:棋盘分割 区间dp

题目

http://noi.openjudge.cn/ch0405/193/
描述
将一个8*8的棋盘进行如下分割:将原棋盘割下一块矩形棋盘并使剩下部分也是矩形,再将剩下的部分继续如此分割,这样割了(n-1)次后,连同最后剩下的矩形棋盘共有n块矩形棋盘。(每次切割都只能沿着棋盘格子的边进行)

原棋盘上每一格有一个分值,一块矩形棋盘的总分为其所含各格分值之和。现在需要把棋盘按上述规则分割成n块矩形棋盘,并使各矩形棋盘总分的均方差最小。
均方差,其中平均值,xi为第i块矩形棋盘的总分。
请编程对给出的棋盘及n,求出O’的最小值。
输入
第1行为一个整数n(1 < n < 15)。
第2行至第9行每行为8个小于100的非负整数,表示棋盘上相应格子的分值。每行相邻两数之间用一个空格分隔。
输出
仅一个数,为O’(四舍五入精确到小数点后三位)。

思路

区间 dp, dp[k][x1][y1][x2][y2] 表示将,(x1,y1):(x2,y2)区间内的矩形分成 k 块,均方误差的最小值
该问题是一个一对多问题,即两个子矩形的最优解拼接为一个大矩形后不一定为最优解,但是一个大矩形的最优解一定可以由两个子矩形的解拼接而成,所以针对子矩形遍历其横向划分和纵向划分。

其中要最小化平方和即可,具体推导过程参考该博客
https://blog.csdn.net/nnnnnnnnnnnny/article/details/51592665

源码

//
//  193.cpp
//  test
//
//  Created by sgxm on 2020/8/3.
//  Copyright © 2020 sgxm. All rights reserved.
//

#include <stdio.h>
#include <iostream>
#include <math.h>
#include <memory.h>
#include <iomanip>
#include <string.h>
#define maxn 15
using namespace std;

int dp[15][8][8][8][8];
int s[8][8][8][8];
int tb[8][8];
int n;
int calSum(int x1,int y1,int x2,int y2){
    int sum = 0;
    for(int i = x1;i<=x2;++i){
        for(int j = y1;j <= y2;++j){
            sum+=tb[i][j];
        }
    }
    return sum*sum;
}
int calDp(int k,int x1,int y1,int x2,int y2){
    int MIN = 99999999;

    if(dp[k][x1][y1][x2][y2]!=-1){
        return dp[k][x1][y1][x2][y2];
    }
    if(k == 1){
        return dp[k][x1][y1][x2][y2]=s[x1][y1][x2][y2];
    }
    dp[k][x1][y1][x2][y2]=MIN;
    //横向划分
    for(int x = x1;x<x2;++x){
        int t1 = calDp(k-1, x1, y1, x, y2);
        int t2 = calDp(k-1,x+1,y1,x2,y2);
        int t = min(t1+s[x+1][y1][x2][y2],t2+s[x1][y1][x][y2]);
        MIN = min(MIN,t);
    }
    for(int y = y1;y<y2;++y){
        int t1 = calDp(k-1,x1,y1,x2,y);
        int t2 = calDp(k-1,x1,y+1, x2, y2);
        int t = min(t1+s[x1][y+1][x2][y2],t2+s[x1][y1][x2][y]);
        MIN = min(MIN,t);
    }
    MIN=min(MIN,dp[k][x1][y1][x2][y2]);
    //cout<<k<< " "<<x1<<" "<<y1<<" "<<x2<<" "<<y2<<" = "<<MIN<<endl;
    return  dp[k][x1][y1][x2][y2]=MIN;

}
int main(){
    cin >> n;
    for(int i = 0;i < 8;++i){
        for(int j = 0;j < 8;++j){
            cin >> tb[i][j];
        }
    }
    memset(dp, -1,sizeof(dp));
    //init Sum
    for(int x1 = 0;x1 < 8;++x1){
        for(int y1 = 0;y1 < 8;++y1){
            for(int x2 = x1;x2<8;++x2){
                for(int y2 = y1;y2<8;++y2){
                    s[x1][y1][x2][y2]=calSum(x1,y1,x2,y2);
                }
            }
        }
    }
    //calDp(2,5,5,6,7);
    int sum2 = calDp(n, 0, 0, 7, 7);
    double res = 1.0*n*calDp(n, 0, 0, 7, 7)-s[0][0][7][7];
    cout<<setiosflags(ios::fixed)<<setprecision(3)<<sqrt(res/(n*n))<<endl;
    return 0;
}

发表评论

邮箱地址不会被公开。 必填项已用*标注