马农

Problem 马农

推荐一条好的博客:https://blog.csdn.net/qq_40155097/article/details/81085840
这个博客写的程序思路非常好,虽然用了哈希,但像我们这些蒟蒻 还是可以看一下下的

问题描述:
在观看完战马检阅之后,来自大草原的两兄弟决心成为超级"马农",专门饲养战马。兄弟两回到草原,将可以养马
的区域,分为N*N的单位面积的正方形,并实地进行考察,归纳出了每个单位面积可以养马所获得的收益。接下来
就要开始规划他们各自的马场了。首先,两人的马场都必须是矩形区域。同时,为了方便两人互相照应,也为了防
止马匹互相走散,规定两个马场的矩形区域相邻,且只有一个交点。最后,互不认输的两人希望两个马场的收益相
当,这样才不会影响他们兄弟的感情。现在,兄弟两找到你这位设计师,希望你给他们设计马场,问共有多少种设
计方案。

输入格式
第一行一个整数N,表示整个草原的大小为N*N。接下来N行,每行N个整数A(i,j),表示第i行第j列的单位草地的收
成。(注意:收益可能是负数,养马也不是包赚的,马匹也可能出现生病死亡等意外。)
N<=50, -1000<A(i,j)<1000

输出格式
输出符合两人要求的草原分配方案数。

样例
输入
3
1 2 3
4 5 6
7 8 9
输出
2
在这里插入图片描述
我们可以先思考一下最暴力的做法
也就是
O(n 10)做法
可以先枚举交叉的那个点(找一各点,必定可以把图分成四部分),然后枚举左上点,枚举右下点(同阶还有右上和左下点要枚举),再求和比对。

但是,你认为这样可以过吗,这是不可能的,看看时间范围,暴力肯定是A不过去的,所以,我们必须要想想怎么去优化他。

简单优化
我们可以通过二维的片段和进行一个优化,就是直接使用二维片段和求和,这样就可以让中间的O(n 4)O(n 4)变成O(1),这样时间复杂度就只剩下O(n 6).

copy

f[i][j]=f[i-1][j]+f[i][j-1]-f[i-1][j-1]+a[i][j];	

可以O(n 6)也是过不了此题的,必须要继续优化。

高度优化
枚举(i,j)是必须要的,不然会有更多没有用的方案出现,那么优化就存在于左上右下的枚举喽!所以往下看:

n的范围是50,那么要在时限以内,算法的复杂度就必须在O(n 4)之内,这样的话就只能枚举两个点,不然就多了,那如果可以去掉交叉点,就只剩下一个点了。

那……把中间的循环拆开,分别枚举两边再合并,那么怎么合并呢?等等再讲,先给大家看一个小学的数学题

如果你有三件上衣和两条裤子,一共有几种搭配法,相信大家这么优秀,肯定会做吧,没错,就是2*3=6种

那如果我们带入这个题目,把左上方当作衣服,左下方当作裤子,分开枚举一次再统计一下数量,应用“乘法原理”乘起来不就好了吗?

没错经过这样优化,复杂度可以达标,可以开始写代码了:

数组怎么开
解释一下这里数组的大小
d数组是用来记录总和的,开的大一点好,看看题目:
开这么大差不多1000×50×50=2500000,但为了保险,再乘2
得5000000.

AC的copy

#include<iostream>
#include<cstdio>
using namespace std;
long long f[2001][2001],a[2001][2001],d[5000001],n,ans;//long long要开,数据这么大,不然自己WA了都不晓得 
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	{
		scanf("%d",&a[i][j]);
		f[i][j]=f[i-1][j]+f[i][j-1]-f[i-1][j-1]+a[i][j];	
	}
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)//枚举交叉的点 
	{
		for(int x=1;x<=i;x++)
		for(int y=1;y<=j;y++)
			d[f[i][j]-f[i][y-1]-f[x-1][j]+f[x-1][y-1]]++;//用来记录左上部分每个数字出现的次数
		for(int x=i+1;x<=n;x++)
		for(int y=j+1;y<=n;y++)
			ans+=d[f[x][y]-f[x][j]-f[i][y]+f[i][j]];//右下部分要直接加,不然会TLE的(血的教训) 
		for(int x=1;x<=i;x++)
		for(int y=1;y<=j;y++)
			d[f[i][j]-f[i][y-1]-f[x-1][j]+f[x-1][y-1]]=0;//清除
//***************************************这是一条分界线************************************************** 
		for(int x=1;x<=i;x++)
		for(int y=j+1;y<=n;y++)
			d[f[i][y]-f[x-1][y]-f[i][j]+f[x-1][j]]++;//如上,记录右上部分每个数字的出现次数 
		for(int x=i+1;x<=n;x++)
		for(int y=1;y<=j;y++)
			ans+=d[f[x][j]-f[i][j]-f[x][y-1]+f[i][y-1]];//如上,左下方也要直接加 
		for(int x=1;x<=i;x++)
		for(int y=j+1;y<=n;y++)
			d[f[i][y]-f[x-1][y]-f[i][j]+f[x-1][j]]=0;//再次清除 
	}
	printf("%d",ans);
	return 0;
}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值