题目描述
输入描述:
输出描述:
示例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);
}