Fake Maxpooling(2020多校第二场F)
@[toc]

题意:

一个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);
}