Fake Maxpooling(2020多校第二场F)

题意:

一个n * m的矩阵,第i行第j列的值是lcm(i,j),然后给定一个 k * k的子矩阵(k<=min(n,m)),然后求出大矩阵中每个子矩阵的最大值的和
看样例:

3 4 2
38

给的矩阵是:
1 2 3 4
2 2 6 4
3 6 3 12
所有2 * 2的子矩阵的最大值分别是 :
2,6,6,6,6,12,总和是38

题解:

暴力求法是O(n m log n),先考虑O(n m)的做法

for(int i=1;i<=n;i++)
{
   
	for(int j=1;j<=m;j++)
	{
   
		if(!Gcd[i][j])
		for(int k=1;k*i<=n&&k*j<=m;k++)
		 Gcd[k*i][k*j]=k,A[k*i][k*j]=i*j*i*k;
	}
}

之后再用单调队列

曾经做过一个叫滑动窗口的题,其实和这个是一个类型,那个是一维的,本题这个是二维的
滑动窗口的题解(含题目链接)
分别求出每行和每列的k个长度区间的最大值
tail表示尾,head表示头,不断得到区间内的最大值,并存进b中,最终覆盖给a
其实就是把二维的拆分成行和列的一维,然后分别进行和滑动窗口那个题一样的操作

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=5e3+5;
int a[maxn][maxn];
int b[maxn];
int qu[maxn];
int n,m,k;
int gcd(int a,int b)
{
   
	if(b==0)return a;
	else return gcd(b,a%b);
}
int lcm(int a,int b)
{
   
	return a*b/gcd(a,b);
}
void getr(int j)//固定列 
{
   
	memset(qu,0,sizeof(qu));
	int head=0;
	int tail=1;
	for(int i=1;i<=n;i++)
	{
   
		while(head<=tail&&qu[head]+k<=i)head++;
		while(head<=tail&&a[i][j]>a[qu[tail]][j])tail--;
		qu[++tail]=i;
		if(i>=k)
		b[i-k+1]=a[qu[head]][j];	
	}
	for(int i=1;i<=n-k+1;i++)
	a[i][j]=b[i];

}
void getl(int i)//固定行 
{
   
	memset(qu,0,sizeof(qu));
	int head=0;
	int tail=1;
	for(int j=1;j<=m;j++)
	{
   
		while(head<=tail&&qu[head]+k<=j)head++;
		while(head<=tail&&a[i][j]>a[i][qu[tail]])tail--;
		qu[++tail]=j;
		if(j>=k)
		b[j-k+1]=a[i][qu[head]];
	}
	for(int j=1;j<=m-k+1;j++)
	a[i][j]=b[j];
}
int main()
{
   
	
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	{
   
		a[i][j]=lcm(i,j);
	}
	for(int i=1;i<=m;i++)
	getr(i);
	for(int i=1;i<=n;i++)
	getl(i);
	long long  sum=0;
	for(int i=1;i<=n-k+1;i++)
	{
   
		for(int j=1;j<=m-k+1;j++)
		{
   
			sum+=a[i][j];
		}
	}
	printf("%lld\n",sum);
}