zjoi2007棋盘分割

国际象棋棋盘裁剪算法

题目描述

国际象棋是世界上最古老的博弈游戏之一,和中国的围棋、象棋以及日本的将棋同享盛名。据说国际象棋起源于易经的思想,棋盘是一个8*8大小的黑白相间的方阵,对应八八六十四卦,黑白对应阴阳。

而我们的主人公小Q,正是国际象棋的狂热爱好者。作为一个顶尖高手,他已不满足于普通的棋盘与规则,于是他跟他的好朋友小W决定将棋盘扩大以适应他们的新规则。

小Q找到了一张由N*M个正方形的格子组成的矩形纸片,每个格子被涂有黑白两种颜色之一。小Q想在这种纸中裁减一部分作为新棋盘,当然,他希望这个棋盘尽可能的大。

不过小Q还没有决定是找一个正方形的棋盘还是一个矩形的棋盘(当然,不管哪种,棋盘必须都黑白相间,即相邻的格子不同色),所以他希望可以找到最大的正方形棋盘面积和最大的矩形棋盘面积,从而决定哪个更好一些。

于是小Q找到了即将参加全国信息学竞赛的你,你能帮助他么?

输入输出格式

输入格式:

包含两个整数N和M,分别表示矩形纸片的长和宽。接下来的N行包含一个N * M的01矩阵,表示这张矩形纸片的颜色(0表示白色,1表示黑色)。

输出格式:

包含两行,每行包含一个整数。第一行为可以找到的最大正方形棋盘的面积,第二行为可以找到的最大矩形棋盘的面积(注意正方形和矩形是可以相交或者包含的)。

输入输出样例

输入样例#1:
3 3
1 0 1
0 1 0
1 0 0
输出样例#1:
4
6

说明

对于20%的数据,N, M ≤ 80

对于40%的数据,N, M ≤ 400

对于100%的数据,N, M ≤ 2000

考虑一维做法 令h[i]为第i个点向左最多扩展多少个  那么答案为max(h[i])

扩展到二维 只需求出第i行第j列向左最多扩展多少个 然后向上向下扫描看看这一行能扩展到哪一列 贡献就是扩展个数*扩展列数

但这是时间复杂度O(n^3) 这时候就可以用单调栈进行优化 

因为较小的扩展个数控制的范围 比 能扩展范围大的 大 我们维护一个单调递增的单调栈 从小到大扫描每列的每行 如果栈顶元素的扩展个数大于当前第i行第j列的扩展个数 那么就说明 栈顶元素向下最多扩展到i-1行 向上最多扩展到第几行可以通过出栈操作更新:若栈顶元素扩展个数大于当前第i行第j列的扩展个数 那么第i行第j列一定能扩展到栈顶元素向上扩展的位置 可以递推计算

最后处理完每列之后要在向栈中塞入0来计算贡献

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn=2000+10;
int s[maxn];
int up[maxn];
int A[maxn][maxn];
int maxd[maxn][maxn];
int main(){
	//freopen("a.in","r",stdin);
	//freopen("a.out","w",stdout);
	int n,m;
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++){
			scanf("%d",&A[i][j]);
			if((i+j)%2==0)
				A[i][j]^=1;
			if(A[i][j]==1)
				maxd[i][j]=maxd[i][j-1]+1;
			else maxd[i][j]=0;
		}
	int top;
	int ans1=0,ans2=0;
	for(int j=1;j<=m;j++){
		top=0;
		for(int i=1;i<=n;i++){
			int to=i;
			while(top&&maxd[s[top]][j]>=maxd[i][j]){
				ans2=max(ans2,maxd[s[top]][j]*(i-up[s[top]]));
				ans1=max(ans1,min(maxd[s[top]][j],(i-up[s[top]]))*min(maxd[s[top]][j],(i-up[s[top]])));
				to=min(to,up[s[top]]);
				top--;
			}
			s[++top]=i;
			up[i]=to;
		}
		while(top&&maxd[s[top]][j]>=0){
			ans2=max(ans2,maxd[s[top]][j]*(n-up[s[top]]));
			ans1=max(ans1,min(maxd[s[top]][j],(n-up[s[top]]))*min(maxd[s[top]][j],(n-up[s[top]])));
			top--;
		}
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++){
			A[i][j]^=1;
			if(A[i][j]==1)
				maxd[i][j]=maxd[i][j-1]+1;
			else maxd[i][j]=0;
		}
	/*for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++)
			printf("%d ",A[i][j]);
		puts("");
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++)
			printf("%d ",maxd[i][j]);
		puts("");
	}*/
	for(int j=1;j<=m;j++){
		top=0;
		for(int i=1;i<=n;i++){
			int to=i;
			while(top&&maxd[s[top]][j]>=maxd[i][j]){
				ans2=max(ans2,maxd[s[top]][j]*(i-up[s[top]]));
				ans1=max(ans1,min(maxd[s[top]][j],(i-up[s[top]]))*min(maxd[s[top]][j],(i-up[s[top]])));
				to=min(to,up[s[top]]);
				top--;
			}
			s[++top]=i;
			up[i]=to;
		}
		while(top&&maxd[s[top]][j]>=0){
			ans2=max(ans2,maxd[s[top]][j]*(n-up[s[top]]));
			ans1=max(ans1,min(maxd[s[top]][j],(n-up[s[top]]))*min(maxd[s[top]][j],(n-up[s[top]])));
			top--;
		}
	}
	printf("%d\n%d\n",ans1,ans2);
return 0;
}



<think>好的,用户想了解棋盘分割的相关概念或实现方法。首先我需要回忆相关的知识,然后结合提供的引用内容来组织回答。 根据引用[1]和[2],棋盘分割问题是将8x8的棋盘分割成n块矩形,使得各块总分的均方差最小。这应该是个动态规划问题,因为需要最优子结构。用户提到的均方差最小化是关键,需要计算所有可能的分割方式,找到最优解。动态规划的状态可能需要记录当前分割的矩形区域和分割次数,转移方程可能涉及不同切割方式的选择。 引用[3]和[4]讨论的是棋盘覆盖问题,使用分治法处理L型骨牌覆盖,这虽然也是棋盘相关,但和用户的问题不同。用户的问题更偏向于分割棋盘以优化均方差,所以需要区分这两个问题,避免混淆。 用户可能想知道具体的算法步骤,比如如何建模、状态转移方程、初始化等。需要解释动态规划的状态定义,比如dp[k][x1][y1][x2][y2]表示将区域(x1,y1)-(x2,y2)分割k次的最小平方和。然后如何通过横向和纵向切割来转移状态。 另外,需要提到预处理二维前缀和,这样可以快速计算任意矩形的总分。这在动态规划中是关键步骤,能提高效率。 代码实现方面,可能需要用多维数组存储状态,循环遍历分割次数和所有可能的矩形区域。引用[2]提到C++实现,但用户可能需要Python的例子,不过根据问题描述,示例代码可能较复杂,但可以给出伪代码或步骤说明。 还要注意问题中的约束条件,比如每次分割必须沿格子边,分割后的剩余部分也必须是矩形。因此每次切割只能将个大矩形分成两个较小的矩形,这会影响状态转移的方式。 最后,用户可能想知道算法的时间复杂度和优化方法,比如如何减少状态数或使用记忆化技术。但因为问题规模是8x8,所以动态规划在合理时间内可行。 总结回答结构:定义问题,动态规划思路,状态转移方程,预处理前缀和,复杂度分析,应用场景。然后生成相关问题。</think>### 棋盘分割算法实现解析 棋盘分割的核心目标是**棋盘分割成n块矩形,使各块总分值的均方差最小**,属于动态规划与数学优化的结合问题[^1][^2]。 --- #### 、问题定义与数学模型 1. **输入**:8×8棋盘,每个格子分值$v_{i,j}$,分割次数$n-1$(最终得到n块) 2. **目标**:最小化均方差: $$ \sigma = \sqrt{\frac{\sum_{i=1}^{n}(X_i - \bar{X})^2}{n}} $$ 其中$X_i$为第i块总分,$\bar{X} = \frac{\sum X_i}{n}$为平均值 --- #### 二、动态规划实现步骤 1. **预处理二维前缀和** 计算$sum[x][y]$表示左上角(1,1)到(x,y)的矩形总分: $$ sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + v_{i,j} $$ 2. **状态定义** $dp[k][x_1][y_1][x_2][y_2]$表示将矩形$(x_1,y_1)-(x_2,y_2)$分割k次的最小平方和 3. **状态转移方程** - **横向切割**:遍历切割位置$x \in [x_1, x_2)$ $$ dp[k][x_1][y_1][x_2][y_2] = \min\left(dp[k-1][x_1][y_1][x][y_2] + calc(x+1,y_1,x_2,y_2)\right) $$ - **纵向切割**:遍历切割位置$y \in [y_1, y_2)$ $$ dp[k][x_1][y_1][x_2][y_2] = \min\left(dp[k-1][x_1][y_1][x_2][y] + calc(x_1,y+1,x_2,y_2)\right) $$ 4. **初始化**分割时($k=0$)的平方和为当前矩形总分平方: $$ dp[0][x1][y1][x2][y2] = (sum[x2][y2] - sum[x1-1][y2] - sum[x2][y1-1] + sum[x1-1][y1-1])^2 $$ --- #### 三、关键代码逻辑(伪代码) ```python # 预处理前缀和 sum = [[0]*9 for _ in range(9)] for i in 1..8: for j in 1..8: sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + v[i][j] # 动态规划初始化 dp = 四维数组 for x1 in 1..8: for y1 in 1..8: for x2 in x1..8: for y2 in y1..8: s = sum[x2][y2] - sum[x1-1][y2] - sum[x2][y1-1] + sum[x1-1][y1-1] dp[0][x1][y1][x2][y2] = s*s # 状态转移 for k in 1..n-1: for x1 in 1..8: for y1 in 1..8: for x2 in x1..8: for y2 in y1..8: min_val = INF # 横向切割 for x in x1..x2-1: val = dp[k-1][x1][y1][x][y2] + calc(x+1,y1,x2,y2) min_val = min(min_val, val) # 纵向切割 for y in y1..y2-1: val = dp[k-1][x1][y1][x2][y] + calc(x1,y+1,x2,y2) min_val = min(min_val, val) dp[k][x1][y1][x2][y2] = min_val # 最终结果计算 total = sum[8][8] mean = total / n result = sqrt(dp[n-1][1][1][8][8]/n - mean*mean) ``` --- #### 四、算法特性 1. **时间复杂度**:$O(n \cdot 8^6)$,因四重矩形循环($8^4$)乘以分割次数$n$ 2. **空间优化**:可复用滚动数组减少空间占用[^2] 3. **应用场景**:图像分块压缩、资源分配优化等需要最小化区域差异的场景 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值