2024牛客暑期多校训练营9 C. Change Matrix 【欧拉函数】

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)=xixjϕ(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=ddϕ(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)=xixjϕ(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,...,ini 才有 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+qlogn)

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值