F题: Matrix and GCD
原题链接:https://ac.nowcoder.com/acm/contest/33194/F
题目大意
给定 n × m n\times m n×m 大小的矩阵 M M M , [ 1 , n m ] [1,nm] [1,nm] 范围内每个整数只在 M M M 中出现一次。定义子矩阵的权值为所有元素的 G C D GCD GCD ,求 M M M 的所有连续子矩阵的权值和。
题解
枚举子矩阵计算
G
C
D
GCD
GCD 的时间复杂度为
O
(
n
2
m
2
)
O(n^2m^2)
O(n2m2) ,显然不现实。
不妨枚举
G
C
D
GCD
GCD ,然后统计符合的子矩阵个数。
设
f
i
f_i
fi 表示
G
C
D
GCD
GCD 为
i
i
i 的倍数的子矩阵个数。
求
f
i
f_i
fi 的过程可以变为:将
i
i
i 的倍数变为
1
1
1 ,非
i
i
i 的倍数变为
0
0
0 ,然后求全
1
1
1 子矩阵个数即可。
这是一个经典问题,参考洛谷P3400,可以在
O
(
n
m
)
O(nm)
O(nm) 的时间复杂度内完成。
若在每次枚举
i
i
i 时重构矩阵,复杂度为
O
(
n
2
m
2
)
O(n^2m^2)
O(n2m2) ,仍不现实。
但是,我们在每次统计时只需要遍历那些为
1
1
1 的位,则总复杂度可降为
∑
i
=
1
n
m
⌊
n
m
i
⌋
\sum\limits_{i=1}^{nm}\lfloor\frac{nm}{i}\rfloor
i=1∑nm⌊inm⌋ ,即
O
(
n
m
l
o
g
n
m
)
O(nmlognm)
O(nmlognm) 级别,可以通过此题。
(实际上在每次遍历时需要对位从左上到右下进行排序,复杂度为
∑
i
=
1
n
m
⌊
n
m
i
⌋
l
o
g
⌊
n
m
i
⌋
\sum\limits_{i=1}^{nm}\lfloor\frac{nm}{i}\rfloor log\lfloor\frac{nm}{i}\rfloor
i=1∑nm⌊inm⌋log⌊inm⌋ ,即
O
(
n
m
l
o
g
2
n
m
)
O(nmlog^2nm)
O(nmlog2nm) 级别,时限为
4
s
4s
4s ,可行)
设
g
i
g_i
gi 表示
G
C
D
GCD
GCD 恰好为
i
i
i 的子矩阵个数。
根据定义,简单容斥可得:
g
i
=
f
i
−
∑
i
∣
j
g
j
g_{i}= f_i-\sum_{i|j}g_j
gi=fi−i∣j∑gj
计算时从大到小枚举保证 i i i 调用的 g j g_j gj 已经被计算过,复杂度为 O ( n m l o g n m ) O(nmlognm) O(nmlognm) 。
最终答案:
a
n
s
=
∑
i
=
1
n
m
g
i
×
i
ans=\sum_{i=1}^{nm}g_i\times i
ans=i=1∑nmgi×i
参考代码
#include<bits/stdc++.h>
using namespace std;
template<class T>inline void read(T&x){
char c,last=' ';
while(!isdigit(c=getchar()))last=c;
x=c^48;
while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+(c^48);
if(last=='-')x=-x;
}
#define LL long long
const int MAXN=1e3+5;
int n,m;
int p[MAXN*MAXN];//数对应的位置
LL f[MAXN*MAXN];
int h[MAXN*MAXN];
int c[MAXN*MAXN];
int stk[MAXN],top;
LL solve(int x){
vector<int>v;
for(int y=x;y<=n*m;y+=x)v.push_back(p[y]);//枚举所有为x倍数的位置
sort(v.begin(),v.end());
for(int t=0;t<(int)v.size();++t){
int x=(v[t]-1)/m+1,y=(v[t]-1)%m+1;
h[(x-1)*m+y]=h[(x-2)*m+y]+1;
}
LL ret=0,sum;
for(int t=0,lastx=0,lasty=0;t<(int)v.size();++t){
int x=(v[t]-1)/m+1,y=(v[t]-1)%m+1;
c[v[t]]=1;
if(x==lastx&&y==lasty+1){//连续的,继续计算
while(top&&h[stk[top]]>=h[v[t]]){
sum-=h[stk[top]]*c[stk[top]];
c[v[t]]+=c[stk[top]];
--top;
}
}
else{//不连续,清空
top=0;
sum=0;
}
sum+=h[v[t]]*c[v[t]];
stk[++top]=v[t];
ret+=sum;
lastx=x,lasty=y;
}
for(int t=0;t<(int)v.size();++t){//注意清空数组
h[v[t]]=c[v[t]]=0;
}
return ret;
}
int main()
{
read(n),read(m);
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
int x;read(x);
p[x]=(i-1)*m+j;//保存位置
}
}
for(int i=1;i<=n*m;++i)f[i]=solve(i);
LL ans=0;
for(int i=n*m;i>=1;--i){
for(int j=i+i;j<=n*m;j+=i)f[i]-=f[j];
//此处计算完成后f[i]的意义为GCD恰好为i的子矩阵个数(即文中的g[i])
ans+=f[i]*i;
}
cout<<ans<<'\n';
return 0;
}