• bzoj 3309 DZY Loves Math


    题目传送门

      传送门I

      传送门II

    题目大意

      定义函数$f(x)$为数$x$所含质因子的最大指数。求$sum_{i = 1}^{a}sum_{j = 1}^{b}f((i, j))$。

      继续用之前的套路统计每个$f$被计算的次数,然后将gcd化为莫比乌斯函数之和。

      $egin{align} sum_{i = 1}^{a}sum_{j = 1}^{b} f((i, j)) &= sum_{d = 1}^{a} sum_{i = 1}^{a}sum_{j = 1}^{b}[(i, j) = d]f(d) \ &= sum_{d = 1}^{a}sum_{i = 1}^{a}sum_{j = 1}^{b} left( sum_{emid i / dwedge e | j / d} mu(e)f(d) ight)\ &= sum_{d = 1}^{a} f(d)sum_{e = 1}^{left lfloor a / d ight floor}left lfloorfrac{a}{de} ight floorleft lfloor frac{b}{de} ight floor mu(e) end{align}$

      如果这样,似乎并没有什么好方法求值,所以试试把那几个向下取整的分数搞到外层来,这样就可以找机会分段处理。于是得到了下面这个式子:

    $sum_{T = 1}^{a}left lfloor frac{a}{T} ight floorleft lfloor frac{b}{T} ight floor sum_{d | T} mu(T / d) f(d)$

      好了问题来了,如何把后面的值快速预处理出来,前面的是可以通过分段降低时间复杂度。

      为了方便描述,所以我们设$g(n) = sum_{d | n}mu(n / d)f(d)$。

      首先讲一下,当$S$是非空有限集合时,$sum_{Asubseteq S}(-1)^{|A|} = 0$。

      首先对n进行质因数分解,得到$n = p_{1}^{alpha_1}p_{2}^{alpha_2}cdots p_{k}^{alpha_k}$。然后设$alpha = maxleft {alpha_{1}, alpha_{2}, dots, alpha_{k} ight }$,$P = left {p_{1}, p_{2}, dots, p_{k} ight }, P' = left { p: pin P wedge p^{alpha} | n ight }$,$overline{P'}$是以$P$为全集,$P'$的补集。

      根据莫比乌斯函数的性质,对于任何一个数有一个质因子的指数大于1,则它的莫比乌斯函数值为0,即为了使式子对答案有贡献,就应当满足,当$d$分解后,$p_i$的指数要么是$alpha_{i}$要么是$alpha_{i} - 1$然后有

    $g(n) = sum_{Asubseteq P}maxleft { a_{i} - [p_{i} in A](-1)^{|A|} ight }$

      显然中间的$maxleft { a_{i} - [p_{i} in A](-1)^{|A|} ight } in left { alpha, alpha - 1 ight }$。所以我们可以把它拆成两部分求和。

      一部分是包含整个$P'$,另一部分是只包含$P'$的一部分。

    $g(n) = sum_{Asubseteq P} [P' subseteq A](a - 1)(-1)^{|A|} + sum_{Asubseteq P}[P' cap Asubset P']a(-1)^{|A|}$

      然后前一部分事实上可以把$A$拆成$overline{P'}$的子集和$P'$的并集,后一部分也可以把$A$拆成$P'$的真子集和$overline{P'}$的子集。

    $egin{align} g(n) &= sum_{Xsubseteq overline{P'}} (a - 1)(-1)^{|X| +|P'|} + sum_{Xsubseteq overline{P'}} sum_{Ysubset P'} (-1)^{|X| + |Y|}a \ &= sum_{Xsubseteq overline{P'}}left [(-1)^{|X| + |P'| + 1} + sum_{Ysubseteq P'} (-1)^{|X| + |Y|}a ight] \ &= (-1)^{|P'| + 1}sum_{Xsubseteq overline{P'}}(-1)^{|X|} end{align}$

      接下来考虑$overline{P'}$。

      1) 如果它不为空的话,那么意味着求和符号下的值为$0$。换言之,就是$n$中的所有幂指数不相等,那么就有$g(n) = 0$。

      2) 如果它为空的话,意味着求和符号的值为$1$,$P'$的大小就是$k$,那么就有$g(n) = (-1)^{k + 1}$。

      然后有什么用呢?

      考虑预处理,由于$10^{7}$的凶残数据范围所以不能够$O(n log n)$预处理。就考虑线性筛。

      这里要用到线性筛一个很重要的性质,每个合数保证被其最小的质因子筛掉。所以,我们只需要记录每个数的最小质因子的次数$pos_i$。

    • 当$prime_j mid i$时,我们就可以轻松处理掉(判断最小质因子的次数是否为$1$,可以得到$g(i)$是否为0)。
    • 当$prime_j | i$的时候,考虑$i$把$prime_j$除尽后的数$x$,此时可以判断一下$pos_x$和$pos_i + 1$是否相等,更新$g$值就好了。

      因此,我们还需要记录每个数把最小质因子除去后的数。这个很好处理。

    Code

     1 /**
     2  * bzoj
     3  * Problem#3309
     4  * Accepted
     5  * Time:11804ms
     6  * Memory:170040k
     7  */
     8 #include <bits/stdc++.h>
     9 #ifndef WIN32
    10 #define Auto "%lld"
    11 #else
    12 #define Auto "%I64d"
    13 #endif
    14 using namespace std;
    15 typedef bool boolean;
    16 
    17 #define LL long long
    18 
    19 const int limit = 1e7;
    20 
    21 int num = 0;
    22 int prime[700100];
    23 boolean vis[limit + 1];
    24 int last[limit + 1];
    25 int posm[limit + 1];
    26 LL g[limit + 1];
    27 inline void Euler() {
    28     memset(vis, false, sizeof(vis));
    29     g[0] = g[1] = -1;
    30     for(int i = 2; i <= limit; i++) {
    31         if(!vis[i])    prime[num++] = i, last[i] = 1, posm[i] = 1, g[i] = 1;
    32         for(int j = 0; j < num && i * 1LL * prime[j] <= limit; j++) {
    33             int c = i * prime[j];
    34             vis[c] = true;
    35             if(i % prime[j]) {
    36                 last[c] = i, posm[c] = 1;
    37                 g[c] = (posm[i] == 1) ? (-g[i]) : (0);
    38             } else {
    39                 last[c] = last[i];
    40                 posm[c] = posm[i] + 1;
    41                 g[c] = (posm[last[c]] == posm[c] || last[c] == 1) ? (-g[last[c]]) : (0);
    42                 break;
    43             }
    44         }
    45     }
    46     for(int i = 2; i <= limit; i++)
    47         g[i] += g[i - 1];
    48 }
    49 
    50 int a, b;
    51 inline void init() {
    52     scanf("%d%d", &a, &b);
    53 }
    54 
    55 inline void solve() {
    56     if(a > b)    swap(a, b);
    57     long long res = 0;
    58     for(int i = 1, j; i <= a; i = j + 1) {
    59         j = min(a / (a / i), b / (b / i));
    60         res += (a / i) * 1LL * (b / i) * (g[j] - g[i - 1]);
    61     }
    62     printf(Auto"
    ", res);
    63 }
    64 
    65 int T;
    66 int main() {
    67     Euler();
    68     scanf("%d", &T);
    69     while(T--) {
    70         init();
    71         solve();
    72     }
    73     return 0;
    74 }
  • 相关阅读:
    Kubernetes实战(第二版)----第1章 Kubernetes简介
    Kubernetes应用程序开发认证(CKAD)学习指南-第3章 配置
    Kubernetes应用程序开发认证(CKAD)学习指南-第2章 核心概念
    Kubernetes应用程序开发认证(CKAD)学习指南-第1章 考试详情和考试资源
    Stream Processing with Apache Flink中文版-- 第11章 接下来学什么
    Stream Processing with Apache Flink中文版-- 第10章 操作Flink和流式应用程序
    Stream Processing with Apache Flink中文版-- 第8章 与外部系统的读写交互
    Stream Processing with Apache Flink中文版-- 第7章 有状态操作符和应用程序
    Stream Processing with Apache Flink中文版-- 第6章 基于时间和窗口的操作符
    Stream Processing with Apache Flink中文版--第5章 DataStream API
  • 原文地址:https://www.cnblogs.com/yyf0309/p/7247805.html
Copyright © 2020-2023  润新知