小郑的疑惑(思维+调和级数)

小郑的疑惑

[link](CSUSTOJ | 小郑的疑惑)

题意

给n个数a和一个长度为m的数组res,a中两两相乘为x,[1,m]中能被x整除的位置的 r e s [ i ] res[i] res[i]加一,最后输出res数组。

题解

如果按照他给的过程模拟的话,是一个 O ( n 2 l o g n ) O(n^2logn) O(n2logn)的复杂度,一定会T。

考虑优化,我们能否将每一个 a i × a j a_i\times a_j ai×aj对应的数先预处出来,因为数据范围只有 1 e 6 1e6 1e6,因此我们可以开一个数组来存每个位置对应的数出现的次数。

发现如果 a i × a j > m a_i \times a_j > m ai×aj>m则不会对答案产生影响,因此我们只需要看 [ 1 , m ] [1,m] [1,m]这个区间内有的数,然后把他们的点对求出来即可。

就是如果本身有着数x,那么我们往后找x的倍数p,如果p的另一个因数d=p/x存在,那么能够得到这个点对的值的方法就是x的数量乘d的数量(注意x和d相等的时候应该是 C 因 子 数 量 2 C_{因子数量}^{2} C2),因为 a i × a j 和 a j × a i a_i\times a_j和a_j\times a_i ai×ajaj×ai的贡献是一个所以我们人为的定一个序,我们只找d >= x的时候的情况,因为如果存在d < x 的情况,那么在枚举到d的时候这个p一定被算过了,这样就可以不重不漏的算出来了。然后再从前往后把每个数给他的倍数的贡献加上叠加上即可,对于每一个数给他的倍数加上,调和级数是 O ( l o g n ) O(logn) O(logn)的,然后对于每次枚举是 O ( n ) O(n) O(n)的,最后时间复杂度大概在 O ( n l o g n ) O(nlogn) O(nlogn)左右。

Code

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <set>
#include <queue>
#include <vector>
#include <map>
#include <bitset>
#include <unordered_map>
#include <cmath> 
#include <stack>
#include <iomanip>
#include <deque> 
#include <sstream>
#define x first
#define y second
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e6 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
LL st[N], res[N], cnt[N];
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) {
        int x; cin >> x;
        st[x] ++;
    }
    for (int i = 1; i <= m; i ++ ) {
        if (st[i]) {
            for (int j = i; j <= m; j += i) {
                LL p = j / i;
                if (p == i) cnt[j] += (st[i] * (st[i] - 1)) / 2;
                else if (p > i)  cnt[j] += st[i] * st[p];
            }
        }
    }
    for (int i = 1; i <= m; i ++ ) 
        for (int j = i; j <= m; j += i )
            res[j] += cnt[i];
    for (int i = 1; i <= m; i ++ ) cout << res[i] << ' ';
    cout << endl;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值