首页 > 其他分享 >调和级数枚举倍数模型

调和级数枚举倍数模型

时间:2023-11-27 22:38:03浏览次数:41  
标签:cnt int 调和级数 枚举 vector 倍数 ans 公因数

调和级数枚举倍数模型

参考博客:

算法学习笔记27:素数筛法【埃氏筛法、线性筛法】

OI&ACM]调和级数枚举倍数模型

板子(时间复杂度\(O(nlogn)\)):
for(int i = 1;i<=n;i++)
{
    for(int j = i;j<=n;j += i)
    {
        ???
	}
}
应用:

目前较常见的用处:

\(f[i]:最大公因数为i的倍数的数对数量\)

通过容斥得到:

\(f[i]:最大公因数恰好为i的数对数量\).

for(int i = mx;i;i--)
{
    for(int j = 2 * i;j <= mx;j += i)
    {
        f[i] -= f[j];
	}
}

CF1034A

解题思路:

我们通过\(a\)数组构造\(b\)数组,\(d = gcd(a),b[i] = \frac{a[i]}{d}\)。

根据题目要求,我们要删掉最少的\(b[i]\),使得剩下的\(b\)的最大公因数大于\(1\)。等价于我们保留最多的\(b[i]\)达到该效果。

我们记录有多少个\(b[i]\)是质数\(x\)的倍数\(cnt[x]\),取最大的\(cnt[x]\)作为保留元素的数量。

答案就是\(n - max(cnt[x])\)。当然,若\(max(cnt[x]) = 0\),无解。

注意:本题值域很大,之所以用质数筛是为了降低时间复杂度,直接用\(2\sim max(a)\)的遍历筛也能保证正确性。

代码:

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
typedef pair<int, int> pii;
#define fi first
#define se second
const int mod = 998244353;
const int N = 1.5e7 + 10;
int primes[N / 10];
int cnt;
bool st[N];
int num[N];
void sieve(int n)
{
    for (int i = 2; i <= n; i++)
    {
        if (!st[i])
        {
            st[i] = true;
            primes[cnt++] = i;
        }
        for (int j = 0; i <= n / primes[j]; j++)
        {
            st[i * primes[j]] = true;
            if (i % primes[j] == 0)
            {
                break;
            }
        }
    }
}

int gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;
}

void solve()
{
    int n;
    cin >> n;
    vector<int> a(n + 1);
    int mx = 0;
    int d = 0;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        d = gcd(d, a[i]);
    }
    for (int i = 1; i <= n; i++)
    {
        a[i] /= d;
        mx = max(mx, a[i]);
        num[a[i]]++;
    }
    int ans = 0;
    for (int i = 0; i < cnt; i++)
    {
        int p = primes[i];
        if (p > mx)
        {
            break;
        }
        for (int j = 2 * p; j <= mx; j += p)
        {
            num[p] += num[j];
        }
        ans = max(ans, num[p]);
    }
    if (ans == 0)
    {
        cout << -1 << endl;
    }
    else
    {
        cout << n - ans << endl;
    }
}

int main()
{
    int t = 1;
    // cin >> t;
    sieve(N - 5);
    while (t--)
    {
        solve();
    }
    return 0;
}

CF1877D题Effects of Anti Pimples

解题思路:

先得到\(f[i]:i的倍数下标元素中的最大值。\)

然后排个序,升序遍历,$ans += f[i] * 2^{i - 1} \(。即,对于前面小于等于自身的\)(i - 1)$个数可选可不选。

代码:

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
typedef pair<int, int> pii;
#define fi first
#define se second
const int mod = 998244353;

void solve()
{
    int n;
    cin >> n;
    vector<int> a(n + 1), f(n + 1);
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    for (int i = 1; i <= n; i++)
    {
        f[i] = 0;
        for (int j = i; j <= n; j += i)
        {
            f[i] = max(f[i], a[j]);
        }
    }
    sort(f.begin() + 1, f.end());
    ll q = 1;
    ll ans = 0;
    for (int i = 1; i <= n; i++)
    {
        ans = (ans + q * f[i]) % mod;
        q = q * 2 % mod;
    }
    cout << ans << endl;
}

int main()
{
    int t = 1;
    // cin >> t;
    while (t--)
    {
        solve();
    }
    return 0;
}

CF803F题Coprime Subsequences

解题思路:

题目要求找到最大公因数为1的子序列的个数。

\(cnt[i]:数组中i的倍数出现的次数\)。

\(f[i]:最大公因数为i的倍数的数对数量\)

\(ans[i]:最大公因数恰好为i的数对数\)

\(ans[i] = f[i] - ans[2 * i] - ans[3*i] - ...-ans[k*i],((k + 1) * i >maxval)\)

\(f[i] = 2^{cnt[i]} - 1\),即数组中\(i\)的倍数出现的元素个数,选或者不选,不能为空。

代码:

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
typedef pair<int, int> pii;
#define fi first
#define se second
const int mod = 1e9 + 7;

void solve()
{
    int n;
    cin >> n;
    vector<int> a(n + 1);
    vector<ll> q(n + 1);
    q[0] = 1;
    int mx = 0;
    vector<int> cnt(1e6 + 10, 0);
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        q[i] = (2 * q[i - 1]) % mod;
        mx = max(mx, a[i]);
        cnt[a[i]]++;
    }
    vector<ll> f(mx + 5), ans(mx + 5);
    // cnt[i]:a数组中a[i]为i的倍数的元素有多少个
    for (int i = 1; i <= mx; i++)
    {
        for (int j = 2 * i; j <= mx; j += i)
        {
            cnt[i] += cnt[j];
        }
    }
    // f[i]:最大公因数为i的倍数的子序列有多少个
    // ans[i]:最大公因数恰好为i的子序列有多少个
    // ans[i] = f[i] - ans[2 * i] - ans[3 * i] - ... - ans[k * i],((k + 1) * i > mx)
    for (int i = mx; i; i--)
    {
        f[i] = q[cnt[i]] - 1;
        ans[i] = f[i];
        for (int j = 2 * i; j <= mx; j += i)
        {
            ans[i] = (ans[i] - ans[j]) % mod;
        }
    }
    ans[1] = (ans[1] + mod) % mod;
    cout << ans[1];
}

int main()
{
    int t = 1;
    // cin >> t;
    while (t--)
    {
        solve();
    }
    return 0;
}

CF1884D题Counting Rhyme

解题思路:

\(cnt[i]:数组中i的倍数出现的次数\)。

\(f[i]:最大公因数为i的倍数的数对数量\)

\(ans[i]:最大公因数恰好为i的数对数\)

得到\(ans[i]\)后,我们遍历整个值域区间,同样用枚举倍数的方法,判断\(i\)的因子是否在数组\(a\)中出现过。

将没有出现过的\(ans[i]\)数对数量加入答案。

代码:

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
typedef pair<int, int> pii;
#define fi first
#define se second
const int mod = 1e9 + 7;

void solve()
{
    int n;
    cin >> n;
    vector<ll> a(n + 1), cnt(n + 1), e(n + 1);
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        cnt[a[i]]++;
        e[a[i]] = true;
    }
    vector<ll> f(n + 1);
    for (int i = 1; i <= n; i++)
    {
        for (int j = 2 * i; j <= n; j += i)
        {
            cnt[i] += cnt[j];
        }
    }
    for (int i = n; i > 0; i--)
    {
        f[i] = (cnt[i] * (cnt[i] - 1)) / 2;
        for (int j = 2 * i; j <= n; j += i)
        {
            f[i] -= f[j];
        }
    }
    vector<bool> st(n + 1, true);
    for (int i = 1; i <= n; i++)
    {
        for (int j = i; j <= n; j += i)
        {
            if (e[i])
            {
                st[j] = false;
            }
        }
    }
    ll ans = 0;
    for (int i = 1; i <= n; i++)
    {
        if (st[i])
        {
            ans += f[i];
        }
    }
    cout << ans << endl;
}

int main()
{
    int t = 1;
    cin >> t;
    while (t--)
    {
        solve();
    }
    return 0;
}

CF1900D - Small GCD

解题思路:

根据式子,不难看出就是遍历三元组全集,所以顺序无关,直接排序同样遍历三元组全集即可。

对排序后的数组取三元组\((a[i],a[j],a[k]),(i < j <k)\),所以\(a[i] \leq a[j] \leq a[k]\)。那么\((a[i],a[j])\)能够做出贡献的次数为\(n - j\)。

如上,按升序遍历每个元素的因子,记录\(f[i]\)。

\(f[i]:最大公因数为i的倍数的数对数量\)。

\(c[x]:为枚举到当前,所有元素中因子x出现的次数\).由于\(c[x]\)的系数随着遍历位置而改变,所以我们边累计\(f[x]\)边更新\(c[x]\)。

\(f[x] = \sum_{i = 1}^{n}(c_i[x] * (n - i))\)。

然后,经典容斥更新\(f[i]\)得到新\(f[i]\)。

\(f[i]:最大公因数恰好为i的数对数量\).

设\(ans[i]:最大公因数恰好为i的数对数量\)。\(maxval 为值域最大值\)。

\(ans[i] = f[i] - ans[2 * i] - ans[3 * i] - ... -ans[k * i],((k + 1) * i > maxval)\)。

最终答案为\(\sum_{i= 1}^{maxval}(f[i] *i)\)

代码:

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
typedef pair<int, int> pii;
#define fi first
#define se second
const int mod = 1e9 + 7;
const int N = 1e5 + 10;
vector<int> b[N];
void solve()
{
    int n;
    cin >> n;
    vector<ll> a(n + 1);
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    sort(a.begin() + 1, a.end());
    vector<ll> c(1e5 + 10);
    // 枚举每个a[i]的因子,计算最大公因数为x的倍数的数对数量
    // f[i]:最大公因数为i的倍数的数对数量
    vector<ll> f(1e5 + 10, 0);
    for (int i = 1; i <= n; i++)
    {
        for (auto x : b[a[i]])
        {
            // 此处a[i]为三元组中第二大的数,数组经过排序
            //  所以能组成的合法三元组为(n - i)个
            f[x] += c[x] * (n - i);
            // 边计数边累加
            c[x]++;
        }
    }

    // 经典容斥求得 f[i] : 最大公因数恰好为i的数对数量
    ll ans = 0;
    for (int i = 1e5; i; i--)
    {
        for (int j = i * 2; j <= 1e5; j += i)
        {
            f[i] -= f[j];
        }
        // 每个数对的贡献值就是i
        ans += f[i] * i;
    }
    // cout << 1 << endl;
    cout << ans << endl;
}

int main()
{
    int t = 1;
    cin >> t;
    // 筛出j的所有因子
    for (int i = 1; i <= N - 5; i++)
    {
        for (int j = i; j <= N - 5; j += i)
        {
            b[j].push_back(i);
        }
    }
    // cout << 1 << endl;
    while (t--)
    {
        solve();
    }
    return 0;
}

标签:cnt,int,调和级数,枚举,vector,倍数,ans,公因数
From: https://www.cnblogs.com/value0/p/17860685.html

相关文章

  • 枚举类
    枚举类概述在某些情况下,一个类的对象是有限而且固定的,例如季节类。只能有四个对象手动实现枚举类:prvate修饰构造器属性使用privatefinal修饰把该类的所有实例都使用publicstaticfinal来修饰使用enum定义枚举类jdk1.5新增的enum关键字用于定义枚举类枚举类和普通类的区别:使用enu......
  • (二十五)C#编程基础复习——enum枚举类型
    枚举类型(也可以成为“枚举器”)由一组具有独立标志服(名称)的整数类型常量构成,在C#枚举类型不仅可以在类或结构体的内部声明,也可以在类或结构体的外部声明,默认情况下枚举类型中成员的默认值是从0开始的,然后逐一递增。在使用枚举时要注意以下几点:枚举类型中不能定义方法;枚举类型具......
  • 6.1 Windows驱动开发:内核枚举SSDT表基址
    SSDT表(SystemServiceDescriptorTable)是Windows操作系统内核中的关键组成部分,负责存储系统服务调用的相关信息。具体而言,SSDT表包含了系统调用的函数地址以及其他与系统服务相关的信息。每个系统调用对应SSDT表中的一个表项,其中存储了相应系统服务的函数地址。SSDT表在64位和32......
  • 6.2 Windows驱动开发:内核枚举SSSDT表基址
    在Windows内核中,SSSDT(SystemServiceShadowDescriptorTable)是SSDT(SystemServiceDescriptorTable)的一种变种,其主要用途是提供Windows系统对系统服务调用的阴影拷贝。SSSDT表存储了系统调用的函数地址,类似于SSDT表,但在某些情况下,Windows系统会使用SSSDT表来对系统服务进行引导......
  • P1029 最大公约数和最小公倍数问题(普及−) 题解
    题目传送门想要做这题,我们要先了解一下最大公约数。最大公因数,也称最大公约数、最大公因子,指两个或多个整数共有约数中最大的一个。a,b的最大公约数记为(a,b),同样的,a,b,c的最大公约数记为(a,b,c),多个整数的最大公约数也有同样的记号。求最大公约数有多种方法,常见的有质因数分解法、短......
  • 求四个数的最小公倍数
    #include<stdio.h>longintfact(longintx,longinty){ inti,j;  for(i=1;i<=x*y;i++) {  if(x%y==0||y%x==0)  {  returnx>y?y:x;  break;  }    j=x*i;  if(j%y==0)  {  returnj;......
  • 枚举类型显式赋值的另一个例子
    enumDay{SUNDAY=-1,MONDAY=3,TUESDAY,WEDNESDAY=2,THURSDAY,FRIDAY,SATURDAY};这些类型的值分别是-1,3,4,2,3,4,5所以从一个显性赋值的变量开始一直到下一个显性赋值的变量结束,中间的变量依次递......
  • 根据值从枚举获取字符串名称
    内容来自DOChttps://q.houxu6.top/?s=根据值从枚举获取字符串名称我有一个如下所示的枚举构造:publicenumEnumDisplayStatus{None=1,Visible=2,Hidden=3,MarkedForDeletion=4}在我的数据库中,枚举被引用为值。我的问题是,如何将枚举的......
  • rust程序设计(6)枚举与模式匹配
    rust中的枚举有什么用?枚举可以嵌入类型的好处是什么你可以在同一个枚举中既有单个值,也有元组或结构体。枚举的每个变体可以拥有不同数量和类型的关联数据。这增加了类型的灵活性和表达力,使你能够更精确地建模你的数据。我知道rust可以为枚举创建方法,那在哪种场景下枚举会比......
  • C枚举类型
    ......