Change Matrix
题意
有一个
n
×
n
n \times n
n×n 的矩阵,位置
(
i
,
j
)
(i, j)
(i,j) 的值为
gcd
(
i
,
j
)
\gcd(i, j)
gcd(i,j)
有
q
q
q 次询问,每次询问选择
x
x
x 行或列,将这一行/列的所有数字,乘上
y
y
y
并对每个查询输出矩阵中所有数字之和,对
1
0
9
+
7
10^9 + 7
109+7 取模
保证每次询问的数据均匀随机
思路
注意到
gcd
(
i
,
j
)
=
∑
x
∣
i
∧
x
∣
j
ϕ
(
x
)
\gcd(i, j) = \sum_{x \mid i \wedge x \mid j} \phi(x)
gcd(i,j)=∑x∣i∧x∣jϕ(x),这是因为:
设
d
=
gcd
(
i
,
j
)
d = \gcd(i, j)
d=gcd(i,j),则
i
,
j
i,j
i,j 的所有公因子都是
d
d
d 的因子,
d
d
d 的所有因子都是
i
,
j
i,j
i,j 的公因子,那么
d
=
∑
d
′
∣
d
ϕ
(
d
′
)
d = \sum_{d^\prime \mid d} \phi (d ^\prime)
d=∑d′∣dϕ(d′) (欧拉函数定理)
即
gcd
(
i
,
j
)
=
∑
x
∣
i
∧
x
∣
j
ϕ
(
x
)
\gcd(i, j) = \sum_{x \mid i \wedge x \mid j} \phi(x)
gcd(i,j)=∑x∣i∧x∣jϕ(x)
考虑维护 n n n 个矩阵,并用贡献统计答案,第 i i i 个矩阵 M i M_i Mi 表示初始欧拉函数 ϕ ( i ) \phi(i) ϕ(i) 对应的矩阵, M i M_i Mi 大小为 ⌊ n i ⌋ \lfloor \dfrac{n}{i} \rfloor ⌊in⌋,这是因为:对于 i i i 来说,它的倍数只有 ⌊ n i ⌋ \lfloor \dfrac{n}{i} \rfloor ⌊in⌋ 个,也就是说只有选择 i , 2 i , 3 i , . . . , ⌊ n i ⌋ ⋅ i i, 2i, 3i, ... , \lfloor \frac{n}{i} \rfloor \cdot i i,2i,3i,...,⌊in⌋⋅i 才有 i i i 作为因子的情况,他的欧拉函数的贡献才会生效
M
i
M_i
Mi 初始全为
ϕ
(
i
)
\phi(i)
ϕ(i),每次将原矩阵的第
x
x
x 行/列乘上
y
y
y 等价于给
x
x
x 的所有因子
d
d
d,将
M
d
M_d
Md 的第
x
d
\frac{x}{d}
dx 行/列乘上
y
y
y
这是因为:选择了
x
x
x,相当于选择了
d
d
d 对应的
x
d
\frac{x}{d}
dx 行/列 的欧拉函数来更新贡献,那么另一维度的矩阵值只需要根据另一维度的变化来选择,其实就是等价的
动态维护每个 M i M_i Mi 的每行每列总计被乘的数 R j R_{j} Rj 和 C j C_{j} Cj ,可以证明: ∑ M i = ϕ ( i ) × ∑ R j × ∑ C j \sum M_i = \phi(i) \times \sum R_{j} \times \sum C_{j} ∑Mi=ϕ(i)×∑Rj×∑Cj
可以证明 ∑ i = 1 n M i \sum_{i = 1}^n M_i ∑i=1nMi 即为答案
时间复杂度: O ( ( n + q ) log n ) O((n + q)\log n) O((n+q)logn)
#include<bits/stdc++.h>
#define fore(i,l,r) for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define ull unsigned long long
#define ALL(v) v.begin(), v.end()
#define Debug(x, ed) std::cerr << #x << " = " << x << ed;
const int INF=0x3f3f3f3f;
const long long INFLL=1e18;
typedef long long ll;
template<class T>
constexpr T power(T a, ll b){
T res = 1;
while(b){
if(b&1) res = res * a;
a = a * a;
b >>= 1;
}
return res;
}
constexpr ll mul(ll a,ll b,ll mod){ //快速乘,避免两个long long相乘取模溢出
ll res = a * b - ll(1.L * a * b / mod) * mod;
res %= mod;
if(res < 0) res += mod; //误差
return res;
}
template<ll P>
struct MLL{
ll x;
constexpr MLL() = default;
constexpr MLL(ll x) : x(norm(x % getMod())) {}
static ll Mod;
constexpr static ll getMod(){
if(P > 0) return P;
return Mod;
}
constexpr static void setMod(int _Mod){
Mod = _Mod;
}
constexpr ll norm(ll x) const{
if(x < 0){
x += getMod();
}
if(x >= getMod()){
x -= getMod();
}
return x;
}
constexpr ll val() const{
return x;
}
explicit constexpr operator ll() const{
return x; //将结构体显示转换为ll类型: ll res = static_cast<ll>(OBJ)
}
constexpr MLL operator -() const{ //负号,等价于加上Mod
MLL res;
res.x = norm(getMod() - x);
return res;
}
constexpr MLL inv() const{
assert(x != 0);
return power(*this, getMod() - 2); //用费马小定理求逆
}
constexpr MLL& operator *= (MLL rhs) & { //& 表示“this”指针不能指向一个临时对象或const对象
x = mul(x, rhs.x, getMod()); //该函数只能被一个左值调用
return *this;
}
constexpr MLL& operator += (MLL rhs) & {
x = norm(x + rhs.x);
return *this;
}
constexpr MLL& operator -= (MLL rhs) & {
x = norm(x - rhs.x);
return *this;
}
constexpr MLL& operator /= (MLL rhs) & {
return *this *= rhs.inv();
}
friend constexpr MLL operator * (MLL lhs, MLL rhs){
MLL res = lhs;
res *= rhs;
return res;
}
friend constexpr MLL operator + (MLL lhs, MLL rhs){
MLL res = lhs;
res += rhs;
return res;
}
friend constexpr MLL operator - (MLL lhs, MLL rhs){
MLL res = lhs;
res -= rhs;
return res;
}
friend constexpr MLL operator / (MLL lhs, MLL rhs){
MLL res = lhs;
res /= rhs;
return res;
}
friend constexpr std::istream& operator >> (std::istream& is, MLL& a){
ll v;
is >> v;
a = MLL(v);
return is;
}
friend constexpr std::ostream& operator << (std::ostream& os, MLL& a){
return os << a.val();
}
friend constexpr bool operator == (MLL lhs, MLL rhs){
return lhs.val() == rhs.val();
}
friend constexpr bool operator != (MLL lhs, MLL rhs){
return lhs.val() != rhs.val();
}
};
const ll mod = 1e9 + 7;
using Z = MLL<mod>;
template<>
ll MLL<0ll>::Mod = 998244353;
std::vector<int> minp;
std::vector<int> primes;
std::vector<int> phi;
void sieve(int n){
minp.assign(n + 5, 0);
primes.clear();
phi.assign(n + 1, 0);
fore(i, 2, n + 1){
if(!minp[i]){
minp[i] = i;
primes.push_back(i);
phi[i] = i - 1;
}
for(auto p : primes){
if(p * i > n) break;
minp[i * p] = p;
if(minp[i] == p){
phi[i * p] = phi[i] * p;
break;
}
phi[i * p] = phi[i] * phi[p];
}
}
}
const int N = 100005;
std::vector<int> divsor[N];
Z Rsum[N];
Z Csum[N];
std::vector<Z> Rmul[N];
std::vector<Z> Cmul[N];
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
sieve(N);
phi[1] = 1;
int n, q;
std::cin >> n >> q;
Z ans = 0;
fore(i, 1, n + 1){
for(int j = i; j <= n; j += i)
divsor[j].push_back(i);
int k = n / i;
Rsum[i] = Csum[i] = k;
Rmul[i].assign(k + 1, 1);
Cmul[i].assign(k + 1, 1);
ans += Rsum[i] * Csum[i] * phi[i];
}
while(q--){
char c;
int x, y;
std::cin >> c >> x >> y;
if(c == 'R'){
for(auto d : divsor[x]){
ans -= Rsum[d] * Csum[d] * phi[d];
Rsum[d] -= Rmul[d][x / d];
Rmul[d][x / d] *= y;
Rsum[d] += Rmul[d][x / d];
ans += Rsum[d] * Csum[d] * phi[d];
}
}
else{
for(auto d : divsor[x]){
ans -= Rsum[d] * Csum[d] * phi[d];
Csum[d] -= Cmul[d][x / d];
Cmul[d][x / d] *= y;
Csum[d] += Cmul[d][x / d];
ans += Rsum[d] * Csum[d] * phi[d];
}
}
std::cout << ans << endl;
}
return 0;
}