“蔚来杯“2022牛客暑期多校训练营9 F题: Matrix and GCD

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=1nminm ,即 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=1nminmloginm ,即 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=fiijgj

计算时从大到小枚举保证 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=1nmgi×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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值