2020牛客暑期多校训练营(第二场)F.Fake Maxpooling

2020牛客暑期多校训练营(第二场)F.Fake Maxpooling

题目链接

题目描述

Given a matrix of size n × m n\times m n×m and an integer k {k} k, where A i , j = l c m ( i , j ) A_{i,j} = lcm(i, j) Ai,j=lcm(i,j)
​ , the least common multiple of i {i} i and j {j} j. You should determine the sum of the maximums among all k × k k\times k k×k submatrices.

输入描述:

Only one line containing three integers n , m , k   ( 1 ≤ n , m ≤ 5000 , 1 ≤ k ≤ min ⁡ { n , m } ) n,m,k~(1\leq n,m \leq 5000, 1 \leq k \leq \min\{n, m\}) n,m,k (1n,m5000,1kmin{n,m}).
输出描述:
Only one line containing one integer, denoting the answer.

示例1

输入

3 4 2

输出

38

比较灵活的一道题,我写了三种方法,喜欢哪一种可以自己看
法一:正解:单调队列,复杂度接近 O(m*n),gcd 函数最好自己写一个,AC代码如下:

#include
using namespace std;
const int N=5e3+5;
typedef long long ll;
int a[N][N],b[N][N];
int gcd(int a,int b){
    return b==0?a:gcd(b,a%b);
}
int main(){
    int n,m,k;
    ll ans=0;
    deque<int>q;
    scanf("%d%d%d",&n,&m,&k);
    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++){
        q.clear();
        for(int j=1;j<=m;j++){
            while(q.size()&&q.front()<=j-k) q.pop_front();//队头的下标超出k个就弹出
            while(q.size()&&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()]);
        }
    }
    for(int j=k;j<=m;j++){
        q.clear();
        for(int i=1;i<=n;i++){
            while(q.size()&&q.front()<=i-k) q.pop_front();
            while(q.size()&&b[q.back()][j]<=b[i][j]) q.pop_back();
            q.push_back(i);
            if(i>=k) ans+=b[q.front()][j];
        }
    }
    printf("%lld",ans);
    return 0;
}

法二:混子 DP,打表可以发现最大值就在点的周围,可以直接进行混子 DP,AC代码如下:

#include
using namespace std;
const int N=5e3+5;
typedef long long ll;
int dp[N][N];
int gcd(int a,int b){
    return b==0?a:gcd(b,a%b);
}
int main(){
    int n,m,k;
    ll ans=0;
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            dp[i][j]=i*j/gcd(i,j);
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(k!=1) dp[i][j]=max({dp[i][j],dp[i-1][j],dp[i][j-1]});
            else dp[i][j]=dp[i][j];
            if(i>=k&&j>=k) ans+=dp[i][j];
        }
    }
    printf("%lld",ans);
    return 0;
}

法三:DFS,从该点位置往左上角 DFS 找最大值即可,时间复杂度其实和法二一样,这种方法可以优化一下,就是当 g c d ( i , j ) = 1 gcd(i,j)=1 gcd(i,j)=1 时可以直接加上 i ∗ j i*j ij,此时一定是最大值,可以打表验证,AC代码如下:

#include
using namespace std;
const int N=5e3+5;
typedef long long ll;
int dp[N][N];
int gcd(int a,int b){
    return b==0?a:gcd(b,a%b);
}
int dfs(int x,int y){
    int a,b;
    if(gcd(x,y)==1) return x*y;
    else{
        a=dfs(x-1,y);
        b=dfs(x,y-1);
    }
    return max(a,b);
}
int main(){
    int n,m,k;
    ll ans=0;
    scanf("%d%d%d",&n,&m,&k);
    for(int i=k;i<=n;i++){
        for(int j=k;j<=m;j++){
            if(k==1) ans+=(i*j)/gcd(i,j);
            else ans+=dfs(i,j);
        }
    }
    printf("%lld",ans);
    return 0;
}

你可能感兴趣的:(单调队列,牛客,DFS)