2020牛客暑期多校训练营Fake Maxpooling(二重单调队列)

Fake Maxpooling

题目描述

在这里插入图片描述

输入描述:

在这里插入图片描述

输出描述:

Only one line containing one integer, denoting the answer

示例1

输入

3 4 2

输出

38

说明

在这里插入图片描述

题目大意

给定一个n* m的矩阵A,A(i,j)=lcm(i,j),求所有A中的所有k* k的子矩阵中元素最大值之和。

分析

首先考虑暴力求,发现复杂度不太对,虽然说有3s的时间,但是还是会TLE。然而A的表是可以暴力先打出来的O(nmlogn),或者用类埃氏筛法的方式O(nm)。

//暴力求解
for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
        a[i][j]=i*j/gcd(i,j);
//类素数筛法
for(int i=1;i<=n;i++)
   	for(int j=1;j<=m;j++)
   		if(!b[i][j])
   			for(int k=1;k*i<=n&&k*j<=m;k++)
   				b[i*k][j*k]=k,a[i*k][j*k]=i*j*k;

然而,就算打出来表了,也无法暴力,复杂度上天。此时就要用到单调队列来算最值了。

单调队列

维护一个单调递减的最长为k的队列(双向),队尾不断加元素,若比前面的元素要大,则弹出前面的比之大的元素。若队列超出了区间范围就弹出队首元素。
如此即可维护每个区间的最值。具体见下图。
在这里插入图片描述
此时3进入队列,由于3>1,所以1被弹出。
在这里插入图片描述
此时-3进入队列,由于-3<3,因此-3合法。
在这里插入图片描述
此时窗口1~-3的最大值即为队首元素3,4进入队列,由于4>3,-3,因此弹出3,-3,压入4.
在这里插入图片描述
所以3~4区间的最大值即为队首元素4。

……(以下皆以此类推)

接下来看这种情况。
在这里插入图片描述
如果接下来压入1,然而4显然不在3~1的范围内,因此要弹出4,然后再压入1。
在这里插入图片描述

本题解法

横着做一次单调队列,存在b数组中,然后再竖着做一次,加起来即可。

代码

#include<bits/stdc++.h>
#define ll long long
#define inf 1<<30
using namespace std;
const int MAXN=5010;
deque<int> q;//双向队列存数据下标
//int gcd(int a,int b){return a%b==0?b:gcd(b,a%b);}
//int lcm(int a,int b){return a/gcd(a,b)*b;}
int a[MAXN][MAXN];
int b[MAXN][MAXN];
int main()
{
    int n,m,k;
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++)
    	for(int j=1;j<=m;j++)
    		if(!b[i][j])
    			for(int k=1;k*i<=n&&k*j<=m;k++)
    				b[i*k][j*k]=k,a[i*k][j*k]=i*j*k;
//    for(int i=1;i<=n;i++){
//    	for(int j=1;j<=m;j++){
//    		cerr<<a[i][j]<<' ';
//		}cerr<<endl;
//	}
    memset(b,0,sizeof(b));
    for(int i=1;i<=n;i++){
    	while(!q.empty()) q.pop_back();//清空队列
		q.push_back(0);//要先放进一个数,否则会运行错误
		for(int j=1;j<=m;j++){
			while(!q.empty()&&j-q.front()>=k) q.pop_front();//若太长了
			while(!q.empty()&&a[i][q.back()]<=a[i][j])
				q.pop_back();q.push_back(j);//若不合法,先弹再压
			b[i][j]=max(b[i][j],a[i][q.front()]);//取最大
		}
	}
	ll ans=0;
	for(int j=k;j<=m;j++){
		while(!q.empty()) q.pop_back();
		q.push_back(0);
		for(int i=1;i<=n;i++){
			while(!q.empty()&&i-q.front()>=k) q.pop_front();
			while(!q.empty()&&b[q.front()][j]<=b[i][j])
				q.pop_back();q.push_back(i);
			if(i>=k) ans+=b[q.front()][j];//前k行没有子矩阵
		}
	}
	printf("%lld\n",ans);
}

END

猜你喜欢

转载自blog.csdn.net/zhangchizc/article/details/107337674