【HDU5749 BestCoder Round 84C】【ST-RMQ?NO!暴力跳法or单调栈法 + 贡献思维】Colmerauer 所有子矩阵size乘鞍点权值和

Colmerauer

 
 Accepts: 32
 
 Submissions: 121
 Time Limit: 10000/5000 MS (Java/Others)
 
 Memory Limit: 131072/131072 K (Java/Others)
问题描述
Peter有一个n \times mn×m的矩阵MM. 定义S(a,b)S(a,b)MM的所有大小为a \times ba×b的子矩阵的权值和. 一个矩阵的权值是这个矩阵所有鞍点的值的和. 在矩阵中, 一个数在所在行中是唯一的最小值, 在所在列中是唯一的最大值, 则被称为鞍点. 帮助Peter找出所有S(a,b)S(a,b)的值.

注意: 本题中鞍点的定义可能和你所知的有所不同.
输入描述
输入包含多组数据, 第一行包含一个整数TT表示测试数据组数. 对于每组数据:

第一行包含两个整数nnmm (1 \le n, m \le 1000)(1n,m1000)表示矩阵的大小.

接下来nn行每行包含mm个非负整数, 表示矩阵MM的每一行 (MM中每个元素都不超过10^6106).
输出描述
对于每组数据, 输出一个整数W = (\displaystyle\sum_{a=1}^{n}\sum_{b=1}^{m}{a \cdot b \cdot S(a,b)}) \text{ mod } 2^{32}W=(a=1nb=1mabS(a,b)) mod 232.
输入样例
3
2 2
1 1
1 1
3 3
1 2 3
4 5 6
7 8 9
3 3
1 2 1
2 3 1
4 5 2
输出样例
4
600
215

【HDU5749 BestCoder Round 84C】【ST-RMQ?NO!暴力跳法 + 贡献思维】Colmerauer 所有子矩阵size乘鞍点权值和

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); }
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b>a)a = b; }
template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b<a)a = b; }
const int N = 1010, M = 0, Z = 1e9 + 7, ms63 = 0x3f3f3f3f;
int casenum, casei;
int n, m;
UI a[N][N];
UI c[N + N];
UI d[N][N];
int lft[N][N];
int rgt[N][N];
int top[N][N];
int bot[N][N];
void RMQinit()
{
	for (int i = 1; i <= n; ++i)
	{
		for (int j = 1; j <= m; ++j)
		{
			if (j == 1)lft[i][j] = 1;
			else
			{
				int p = j - 1;
				while (p >= 1 && a[i][p] > a[i][j])p = lft[i][p] - 1;
				lft[i][j] = p + 1;
			}
			if (i == 1)top[i][j] = 1;
			else
			{
				int p = i - 1;
				while (p >= 1 && a[p][j] < a[i][j])p = top[p][j] - 1;
				top[i][j] = p + 1;
			}
		}
	}

	for (int i = n; i >= 1; --i)
	{
		for (int j = m; j >= 1; --j)
		{
			if (j == m)rgt[i][j] = m;
			else
			{
				int p = j + 1;
				while (p <= m && a[i][p] > a[i][j])p = rgt[i][p] + 1;
				rgt[i][j] = p - 1;
			}
			if (i == n)bot[i][j] = n;
			else
			{
				int p = i + 1;
				while (p <= n && a[p][j] < a[i][j])p = bot[p][j] + 1;
				bot[i][j] = p - 1;
			}
		}
	}
}

int main()
{
	//fre();
	for (int i = 1; i <= 2000; ++i)c[i] = c[i - 1] + i;
	for (int i = 1; i <= 1000; ++i)
	{
		for (int j = 1; j <= 1000; ++j)
		{
			d[i][j] = d[i][j - 1] + c[i + j - 1] - c[j - 1];
		}
	}
	scanf("%d", &casenum);
	for (casei = 1; casei <= casenum; ++casei)
	{
		scanf("%d%d", &n, &m);
		for (int i = 1; i <= n; ++i)
		{
			for (int j = 1; j <= m; ++j)
			{
				scanf("%u", &a[i][j]);
			}
		}
		RMQinit();
		UI ans = 0;
		for (int i = 1; i <= n; ++i)
		{
			for (int j = 1; j <= m; ++j)
			{
				int topp = top[i][j];
				int bott = bot[i][j];
				int lftt = lft[i][j];
				int rgtt = rgt[i][j];
				ans += d[i - topp + 1][bott - i + 1] * d[j - lftt + 1][rgtt - j + 1] * a[i][j];
			}
		}
		printf("%u\n", ans);
	}
	return 0;
}
/*
【trick&&吐槽】
复杂度,复杂度,复杂度!
复杂度包括——
思维复杂度
代码复杂度
时空复杂度。

这道题上来就想到求出每个点,作为鞍点,向左向右可以延展多少,向上向下可以延展多少。
不过是想用ST-RMQ实现。
而ST-RMQ的复杂度多了一个log(n)

妈蛋,zimpha的数据竟然不水了,多个log的算法就tle了。
>.<虽然节省了思维复杂度,却浪费了代码复杂度与时空复杂度,最后没有AC。
还是酌情想出高效的算法比较好。

【题意】
给你一个n*m的矩阵。
我们定义S(a,b)为所有大小为a*b的子矩阵的权值和。
一个子矩阵的权值和,定义为——
该矩阵中有所有鞍点的权值和。

一个矩阵中的鞍点,定义为——
该点同行的所有点中,该点为唯一最小的点。
该点同列的所有点中,该点为唯一最大的点。

让你输出∑(a=1~n)∑(b=1~m)a*b*S(a,b)

【类型】
贪心思维 单调思维

【分析】
这题用ST-RMQ的算法会导致tle
于是我们要尝试用O(nm)的算法AC这道题。
也就是说,用均摊O(1)的复杂度,求出——
1,每个点向左最多延伸多少个数,都比它大
2,每个点向右最多延伸多少个数,都比它大
3,每个点向上最多延伸多少个数,都比它小
4,每个点向下最多延伸多少个数,都比它小

这个的处理方式,可以是本程序的暴力跳。
比如我们处理,"每个点向左最多延伸多少个数,都比它大"时,
int p = j - 1;
while (p >= 1 && a[i][p] > a[i][j])p = lft[i][p] - 1;
lft[i][j] = p + 1;

我们对每个点,处理其向左可以延展的最大区间段。
如果发现再向左所延展的区间点,小于等于它,那延展失效。
否则便可以继续向左延展。
这个延展的过程,实际上也是区间合并。
显然我们一共只有n个区间,每次向左延展,也意味着多一次的区间合并。
而每个区间最多被合并一次,所以对于每行的m个数,处理lft[][]的总的复杂度为O(m)
即单点均摊复杂度为O(m),总复杂度为O(nm)

当然,这个过程还可以通过单调栈实现。
以处理lft为例。
我们维护一个严格上升的单调栈。
对于每一个位点,
如果其之前栈顶元素大于它,那么该点的左延展区间范围,就可以覆盖到该栈顶元素,我们同时做出栈处理。
直到——使得其之前栈顶元素小于等于它,那么该点就无法向左延展了,其延展的极限也就知道了。

因为每个点最多入栈一次,出栈一次。所以每点的均摊复杂度为O(1)

=======================
当我们处理完每个点向左向右向上向下的最大延展范围后。
那这个点有贡献的矩阵,
向上的延展长度,可以是[1,top]
向下的延展长度,可以是[1,bot]
向左的延展长度,可以是[1,lft]
向右的延展长度,可以是[1,rgt]

也就是说,包括该点的

【时间复杂度&&优化】
O(nm)

【数据】
100 
5 5 
3 0 7 9 10 
4 9 9 9 7 
7 1 7 1 10 
3 1 8 6 1 
2 2 6 7 10 

*/
【HDU5749 BestCoder Round 84C】【ST-RMQ?NO!单调栈法 + 贡献思维】Colmerauer 所有子矩阵size乘鞍点权值和

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); }
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b>a)a = b; }
template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b<a)a = b; }
const int N = 1010, M = 0, Z = 1e9 + 7, ms63 = 0x3f3f3f3f;
int casenum, casei;
int n, m;
int a[N][N];
UI c[N + N];
UI d[N][N];
int lft[N][N];
int rgt[N][N];
int top[N][N];
int bot[N][N];

int sta[N], tp;
void RMQinit()
{
	for (int i = 1; i <= n; ++i)
	{
		sta[tp = 0] = 0; a[i][0] = -1e9;
		for (int j = 1; j <= m; ++j)
		{
			while (a[i][sta[tp]] > a[i][j])--tp;
			lft[i][j] = sta[tp] + 1;
			//if (a[i][sta[tp]] == a[i][j])--tp;
			sta[++tp] = j;
		}
	}
	for (int i = 1; i <= n; ++i)
	{
		sta[tp = 0] = m + 1; a[i][m + 1] = -1e9;
		for (int j = m; j >= 1; --j)
		{
			while (a[i][sta[tp]] > a[i][j])--tp;
			rgt[i][j] = sta[tp] - 1;
			//if (a[i][sta[tp]] == a[i][j])--tp;
			sta[++tp] = j;
		}
	}
	for (int j = 1; j <= m; ++j)
	{
		sta[tp = 0] = 0; a[0][j] = 1e9;
		for (int i = 1; i <= n; ++i)
		{
			while (a[sta[tp]][j] < a[i][j])--tp;
			top[i][j] = sta[tp] + 1;
			//if (a[sta[tp]][j] == a[i][j])--tp;
			sta[++tp] = i;
		}
	}
	for (int j = 1; j <= m; ++j)
	{
		sta[tp = 0] = n + 1; a[n + 1][j] = 1e9;
		for (int i = n; i >= 1; --i)
		{
			while (a[sta[tp]][j] < a[i][j])--tp;
			bot[i][j] = sta[tp] - 1;
			//if (a[sta[tp]][j] == a[i][j])--tp;
			sta[++tp] = i;
		}
	}
}

int main()
{
	for (int i = 1; i <= 2000; ++i)c[i] = c[i - 1] + i;
	for (int i = 1; i <= 1000; ++i)
	{
		for (int j = 1; j <= 1000; ++j)
		{
			//d[i][j] = d[i][j - 1] + c[i + j - 1] - c[j - 1];	//第一种计算方法
			d[i][j] = i*c[j] + j*c[i] - i*j;					//第二种计算方法
			
		}
	}
	scanf("%d", &casenum);
	for (casei = 1; casei <= casenum; ++casei)
	{
		scanf("%d%d", &n, &m);
		for (int i = 1; i <= n; ++i)
		{
			for (int j = 1; j <= m; ++j)
			{
				scanf("%d", &a[i][j]);
			}
		}
		RMQinit();
		UI ans = 0;
		for (int i = 1; i <= n; ++i)
		{
			for (int j = 1; j <= m; ++j)
			{
				int topp = top[i][j];
				int bott = bot[i][j];
				int lftt = lft[i][j];
				int rgtt = rgt[i][j];
				ans += d[i - topp + 1][bott - i + 1] * d[j - lftt + 1][rgtt - j + 1] * a[i][j];
			}
		}
		printf("%u\n", ans);
	}
	return 0;
}
/*
【trick&&吐槽】
复杂度,复杂度,复杂度!
复杂度包括——
思维复杂度
代码复杂度
时空复杂度。

这道题上来就想到求出每个点,作为鞍点,向左向右可以延展多少,向上向下可以延展多少。
不过是想用ST-RMQ实现。
而ST-RMQ的复杂度多了一个log(n)

妈蛋,zimpha的数据竟然不水了,多个log的算法就tle了。
>.<虽然节省了思维复杂度,却浪费了代码复杂度与时空复杂度,最后没有AC。
还是酌情想出高效的算法比较好。

【题意】
给你一个n*m的矩阵。
我们定义S(a,b)为所有大小为a*b的子矩阵的权值和。
一个子矩阵的权值和,定义为——
该矩阵中有所有鞍点的权值和。

一个矩阵中的鞍点,定义为——
该点同行的所有点中,该点为唯一最小的点。
该点同列的所有点中,该点为唯一最大的点。

让你输出∑(a=1~n)∑(b=1~m)a*b*S(a,b)

【类型】
贪心思维 单调思维

【分析】
这题用ST-RMQ的算法会导致tle
于是我们要尝试用O(nm)的算法AC这道题。
也就是说,用均摊O(1)的复杂度,求出——
1,每个点向左最多延伸多少个数,都比它大
2,每个点向右最多延伸多少个数,都比它大
3,每个点向上最多延伸多少个数,都比它小
4,每个点向下最多延伸多少个数,都比它小

这个的处理方式,可以是本程序的暴力跳。
比如我们处理,"每个点向左最多延伸多少个数,都比它大"时,
int p = j - 1;
while (p >= 1 && a[i][p] > a[i][j])p = lft[i][p] - 1;
lft[i][j] = p + 1;

我们对每个点,处理其向左可以延展的最大区间段。
如果发现再向左所延展的区间点,小于等于它,那延展失效。
否则便可以继续向左延展。
这个延展的过程,实际上也是区间合并。
显然我们一共只有n个区间,每次向左延展,也意味着多一次的区间合并。
而每个区间最多被合并一次,所以对于每行的m个数,处理lft[][]的总的复杂度为O(m)
即单点均摊复杂度为O(m),总复杂度为O(nm)

当然,这个过程还可以通过单调栈实现。
以处理lft为例。
我们维护一个严格上升的单调栈。
对于每一个位点,
如果其之前栈顶元素大于它,那么该点的左延展区间范围,就可以覆盖到该栈顶元素,我们同时做出栈处理。
直到——使得其之前栈顶元素小于等于它,那么该点就无法向左延展了,其延展的极限也就知道了。

因为每个点最多入栈一次,出栈一次。所以每点的均摊复杂度为O(1)

=======================
当我们处理完每个点向左向右向上向下的最大延展范围后,我们要开始算贡献了——

对于每个点,这个点作为鞍点,那这个点所得到贡献的矩阵——
向上的延展长度,可以是[1,top]
向下的延展长度,可以是[1,bot]
向左的延展长度,可以是[1,lft]
向右的延展长度,可以是[1,rgt]
其贡献,也就是
int y=0, x=0;
for(int i=1;i<=top;++i)
{
	for(int j=1;j<=bot;++j)
	{
		y+=(i+j-1);
	}
}
for(int i=1;i<=lft;++i)
{
	for(int j=1;j<=rgt;++j)
	{
		x+=(i+j-1);
	}
}
ans+=y*x*a[i][j]

我们的做法有以下几种——
1,预处理for(int i=1;i<=a;++i)for(int j=1;j<=b;++j)tmp+=i+j-1;
这个的预处理,d[i][j]是在d[i][j-1]的基础上,获得了c[i+j-1]-c[j-1]的增长
2,独立(i)与(j)算贡献,显然——
	for(int i=1;i<=a;++i)for(int j=1;j<=b;++j)tmp+=i+j-1;
	有b个(1~a),有a个(1~b),再减去a*b个1

【时间复杂度&&优化】
O(nm)

【数据】
100
5 5
3 0 7 9 10
4 9 9 9 7
7 1 7 1 10
3 1 8 6 1
2 2 6 7 10

*/


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值