UESTC2022暑假前(?)集训 数学与几何

数学与几何解题报告

其实还有一个求圆上的整点个数的题,写完忘保存丢掉了。

B-新月之舞 推式子

题意

i=1nj=1nimodj

1n1012

解题思路

=i=1nj=1iimodj+i=1nj=i+1nimodj

=i=1nj=1i(iijj)+i=1ni(ni)

=i=1ni2i=1nj=1iijj+i=1ni(ni)

$=\sum\limits_{i=1}{n}ni-\sum\limits_{i=1}\sum\limits_{j=1}^{i} \left \lfloor \frac{i}{j} \right \rfloor j $

$=\frac{n2(n+1)}{2}-\sum\limits_{i=1}\sum\limits_{j=1}^{i} \left \lfloor \frac{i}{j} \right \rfloor j $

结论:j=1iijj=j=1iσ(j)

证明:考虑[1,i]中的每个数出现在了几个σ(j),有j=1iσ(j)=j=1id|jd=d=1iidd=j=1iijj

因此i=1nj=1iijj=i=1nj=1iσ(j)

=i=1nσ(i)(n+1i)

=(n+1)i=1nσ(i)i=1niσ(i)

根据前面的结论,

(n+1)i=1nσ(i)=(n+1)d=1ndnd

只需求出i=1niσ(i)

类似地,我们考虑id对答案的贡献,可以得到

i=1niσ(i)=i=1nd|iid=d=1n(d+2d++ndd)d

=d=1nnd(nd+1)2d2

综上所述,i=1nj=1nimodj=n2(n+1)2(n+1)d=1ndnd+d=1nnd(nd+1)2d2

借助数论分块,可以O(n)求出答案。

数论分块是这么一个东西:

出现ni时,我们可以把n个数相加,转化成O(n)个区间和相加,每一个区间里ni是相等的。

例如最简单的H(n)=i=1nni

int H(int n) {
    int res = 0;
    for (int l = 1, r; l <= n; l = r + 1) {
        r = n / (n / l);
        res += (r - l + 1) * (n / l);
    }
    return res;
}

答案会达到O(n3),需要使用int128。

代码

//
// Created by vv123 on 2022/7/4.
//
#include <bits/stdc++.h>
#define int __int128
using namespace std;

inline int read() {
    int x = 0, f = 1; char ch = getchar();
    while (ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9'){ x = x * 10 + ch - '0'; ch = getchar(); }
    return x * f;
}
inline void print(int x) {
    if(x < 0) { putchar('-'); x = -x; }
    if(x > 9) print(x / 10);
    putchar(x % 10 + '0');
}

int n, ans;


inline int A() {
    int res = 0;
    for (int l = 1, r; l <= n; l = r + 1) {
        r = n / (n / l);
        res += (l + r) * (r - l + 1) * (n / l) / 2;
    }
    return res;
}

inline int sumsquare(int x) {
    return x * (x + 1) * (x + x + 1) / 6;
}

inline int B() {
    int res = 0;
    for (int l = 1, r; l <= n; l = r + 1) {
        r = n / (n / l);
        int t = n / l;
        res += (sumsquare(r) - sumsquare(l - 1)) * t * (t + 1) / 2;
    }
    return res;
}

void solve() {
    n = read();
    ans =  n * n * (n + 1) / 2 - (n + 1) * A() + B();
    print(ans);putchar('\n');
}

signed main() {
    int T = read();
    while (T--) {
        solve();
    }
    return 0;
}

C-回转数 几何

题意

给出一段由n条有向线段组成的有向封闭折线,可能有交叉和重叠,并给出m个点,对每个点求折线围绕该点的回转数。

3n5000,1m5000

解题思路

  • to-left测试:判断一个点P是否在一条有向直线AB左侧,只需要看AB×AP是否大于0.

  • 回转数:面内闭合曲线逆时针绕过该点的总次数。性质:当回转数为0,点在曲线外部。

  • 朴素做法:是计算相邻两边夹角的和,需要使用浮点数。

  • 无精度误差的解法:向右作一条射线,与朝上的边相交回转数+1,朝下的边相交回转数-1。

  • 朝上的边与射线相交,当且仅当检测点纵坐标介于uv之间,且检测点在uv左侧。另一种情况同理。

    1658151099750

  • 细节:如果射线恰好经过顶点,不妨定义顶点在射线上方。(只统计minyyp<maxy产生的答案)

代码

//
// Created by vv123 on 2022/7/18.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;

const int N = 2e4 + 10;
struct point {
    int x, y;
    point operator-(const point &p) const { return {x - p.x, y - p.y}; }
    int operator*(const point &p) const { return x * p.x + y * p.y; }
    int operator^(const point &p) const { return x * p.y - p.x * y; }
    int quad() const {
        if (y > 0 || (y == 0 && x >= 0)) return 1;
        return 2;
    }//下平面定义为[-180,0),上平面定义为[0,180)
    int toleft(const point &p) { //p在这个向量的逆时针方向,则叉积为正
        int t = (*this) ^ p;
        if (t > 0) return 1;
        if (t == 0) return 0;
        if (t < 0) return -1;
    }
};

bool cmp(const point &a,const point &b) {
    if (a.quad() != b.quad()) return a.quad() < b.quad();
    else return (a ^ b) > 0;
}

struct line {
    point p, v;
    int toleft(const point &a) {
        return v.toleft(a - p);
    }
};

struct polygon {
    vector<point> vec;
    int nxt(int i) { return (i + 1) % vec.size(); }
    pii winding(const point &p) {
        int res = 0;
        for (int i = 0; i < vec.size(); i++) {
            point u = vec[i], v = vec[nxt(i)];
            if (((p - u) ^ (p - v)) == 0 && (p - u) * (p - v) <= 0) return {1, 0};
            if (u.y == v.y) continue;
            line uv = {u, v - u};
            if (u.y < v.y && uv.toleft(p) <= 0) continue;
            if (u.y > v.y && uv.toleft(p) >= 0) continue;
            if (u.y < p.y && v.y >= p.y) res++;
            if (u.y >= p.y && v.y < p.y) res--;
        }
        return {0, res};
    };
} poly;

void solve() {
    int n, m;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        int x, y;
        cin >> x >> y;
        poly.vec.push_back({x, y});
    }
    cin >> m;
    while (m--) {
        int x, y;
        cin >> x >> y;
        point p = {x, y};
        pii res = poly.winding(p);
        if (res.first == 1) puts("EDGE");
        else cout << res.second << "\n";
    }
}
signed main() {
    int T = 1;
    //cin >> T;
    while (T--) {
        solve();
    }
    return 0;
}

E-兔儿爷 几何

题意

有三瓶药水,每瓶药水两种溶质的浓度分别为ai,bin次询问问能否配制出两种溶质浓度分别为x,y的药水。

1n105

解题思路

容易发现"把两种药水按一定比例混合",结果相当于"两个向量按一定比例相加"(系数非负,且和为1)。

我们可以将问题转化成,给定三个向量,是否存在一组和为1的非负系数,使得三个向量的线性组合为(x,y)

或者说,(x,y)能否被表达为三个向量的凸组合。

1658247361595

根据凸包的定义,只需要检验(x,y)是否在三瓶药水构成的凸包(三角形)内即可。

这里直接采用了C题的回转数法。

代码

//
// Created by vv123 on 2022/7/18.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;

const int N = 2e4 + 10;
struct point {
    int x, y;
    point operator-(const point &p) const { return {x - p.x, y - p.y}; }
    int operator*(const point &p) const { return x * p.x + y * p.y; }
    int operator^(const point &p) const { return x * p.y - p.x * y; }
    int quad() const {
        if (y > 0 || (y == 0 && x >= 0)) return 1;
        return 2;
    }//下平面定义为[-180,0),上平面定义为[0,180)
    int toleft(const point &p) { //p在这个向量的逆时针方向,则叉积为正
        int t = (*this) ^ p;
        if (t > 0) return 1;
        if (t == 0) return 0;
        if (t < 0) return -1;
    }
};

bool cmp(const point &a,const point &b) {
    if (a.quad() != b.quad()) return a.quad() < b.quad();
    else return (a ^ b) > 0;
}

struct line {
    point p, v;
    int toleft(const point &a) {
        return v.toleft(a - p);
    }
};

struct polygon {
    vector<point> vec;
    int nxt(int i) { return (i + 1) % vec.size(); }
    pii winding(const point &p) {
        int res = 0;
        for (int i = 0; i < vec.size(); i++) {
            point u = vec[i], v = vec[nxt(i)];
            if (((p - u) ^ (p - v)) == 0 && (p - u) * (p - v) <= 0) return {1, 0};
            if (u.y == v.y) continue;
            line uv = {u, v - u};
            if (u.y < v.y && uv.toleft(p) <= 0) continue;
            if (u.y > v.y && uv.toleft(p) >= 0) continue;
            if (u.y < p.y && v.y >= p.y) res++;
            if (u.y >= p.y && v.y < p.y) res--;
        }
        return {0, res};
    };
} poly;

void solve() {
    int n, m;
    //cin >> n;
    n = 3;
    for (int i = 1; i <= n; i++) {
        int x, y;
        cin >> x >> y;
        poly.vec.push_back({x, y});
    }
    cin >> m;
    while (m--) {
        int x, y;
        cin >> x >> y;
        point p = {x, y};
        pii res = poly.winding(p);
        if (res.first == 1 || res.second != 0) puts("YES");
        else puts("NO");
    }
}
signed main() {
    int T = 1;
    //cin >> T;
    while (T--) {
        solve();
    }
    return 0;
}


F-多重和式 反演

题意

给定n,m,a(1n,m,a107), 求i=1nj=1mlcm(i,j)[gcd(i,j)a]

解题思路

  • 莫比乌斯函数:μ(n)={1n=1(1)|P|n=ipi0pi,pi2n
  • 反演结论:d|nμ(d)=[n=1]
  • 莫比乌斯变换:
    • 形式1 F(n)=d|nf(d)f(n)=d|nμ(d)F(nd)
    • 形式2 F(n)=n|df(d)f(n)=n|dμ(dn)F(d)
  • 基础例题:i=1nj=1m[gcd(i,j)=1]=i=1nj=1md|gcd(i,j)μ(d)=d=1nμ(d)nd2(考虑枚举d)

考虑将lcm转化为gcd,有

i=1nj=1mlcm(i,j)[gcd(i,j)a]=i=1nj=1mijgcd(i,j)[gcd(i,j)a]

=d=1min(n,m,a)i=1nj=1mijd[gcd(i,j)=d]

注意到两个数的gcd等于d,当且仅当它们除以d后互质。

=d=1min(n,m,a)i=1ndj=1mdidjdd[gcd(i,j)=1]

=d=1min(n,m,a)di=1ndj=1mdij[gcd(i,j)=1]

g(n,m)=i=1ndj=1mdij[gcd(i,j)=1],则原式=d=1min(n,m,a)dg(nd,md)

考虑如何求出g(n,m).

f(d)=i=1nj=1mij[gcd(i,j)=d](n,m为已确定的常数)

那么f(1)=g(n,m)

F(x)=i=1nj=1mij[x|gcd(i,j)],那么F(x)=x|df(d)

根据形式2,f(1)=dmin(n,m)μ(d)F(d)

下面考虑如何快速求出F(d)。根据前面类似的手法,可以得到

F(d)=i=1ndj=1mdidjd[gcd(i,j)=1]=d2nd(nd+1)2md(md+1)2

从而g(n,m)=f(1)=dmin(n,m)μ(d)d2nd(nd+1)2md(md+1)2

代入原式=d=1min(n,m,a)dg(nd,md)求解即可

使用了两层整除分块,时间复杂度为O(n)

这里第一次接触二维的整除分块。一般只需要将$r =\left \lfloor \frac{n}{\left \lfloor \frac{n}{l} \right \rfloor} \right \rfloor r =\min(\left \lfloor \frac{n}{\left \lfloor \frac{n}{l} \right \rfloor} \right \rfloor,\left \lfloor \frac{m}{\left \lfloor \frac{m}{l} \right \rfloor} \right \rfloor)rar =\min(\left \lfloor \frac{n}{\left \lfloor \frac{n}{l} \right \rfloor} \right \rfloor,\left \lfloor \frac{m}{\left \lfloor \frac{m}{l} \right \rfloor} \right \rfloor, a)$

代码

//
// Created by vv123 on 2022/7/19.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;


int n, m, a, ans;
const int mod = 1e9 + 7;
const int N = 1e7 + 10;

int pow(int a, int b, int p) {
    int res = 1;
    for (; b; b >>= 1) {
        if (b & 1) res = res * a % mod;
        a = a * a % mod;
    }
    return res;
}

int vis[N], prime[N], mu[N];

int s[N], sum[N];

void Shai(int n = N - 1) {
    vis[1] = mu[1] = 1;
    int cnt = 0;
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) {
            prime[++cnt] = i;
            mu[i] = -1;
        }
        for (int j = 1; j <= cnt && i * prime[j] <= n; j++) {
            vis[i * prime[j]] = 1;
            if (i % prime[j] == 0) {
                mu[i * prime[j]] = 0; //p1的指数大于1,莫比乌斯函数为0
                break;
            } else {
                mu[i * prime[j]] = -mu[i];
            }
        }
    }
    s[0] = sum[0] = 0;
    for (int i = 1; i <= n; i++) {
        s[i] = (s[i - 1] + i * i % mod * mu[i] % mod) % mod;
        sum[i] = (sum[i - 1] + i) % mod;
    }
}



inline int g(int n, int m) {
    int res = 0;
    for (int l = 1, r; l <= min(n, m); l = r + 1) {
        r = min(n / (n / l), m / (m / l));
        int t = (n / l) * (n / l + 1) % mod * (m / l)  %  mod * (m / l + 1)  % mod * pow(4, mod - 2, mod) % mod;
        res = (res + (s[r] - s[l - 1] + mod) % mod * t % mod) % mod;
    }
    return res;
}

void solve() {
    Shai();
    cin >> n >> m >> a;
    int ans = 0;
    for (int l = 1, r; l <= min(min(n, m), a); l = r + 1) {
        r = min(a, min(n / (n / l), m / (m / l)));
        int t = g(n / l, m / l);
        //cout << l << " " << r << " " << (sum[r] - sum[l - 1] + mod) % mod * t % mod << "\n";
        ans = (ans + (sum[r] - sum[l - 1] + mod) % mod * t % mod) % mod;
    }
    cout << ans << "\n";
}

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

G-数三角形 几何

题意

在二维平面上有 n个点,请问以这些点为顶点可以形成多少个不同的锐角三角形。(保证坐标两两不同)

3n2000

解题思路

我们将锐角定义为[0,π2),非锐角定义为[π2,π]。那么任选三个点,只有两种情况:

  1. 有一个非锐角和两个锐角。包括直角三角形、钝角三角形和三点共线。
  2. 有三个锐角,为锐角三角形。

因此设锐角、非锐角的个数分别为R,D,显然锐角三角形的个数为R2D3

又因为总共有n(n12)个角,只需统计出非锐角的个数。考虑枚举每一个点作为顶点,将其他点进行极角排序。

这里将极角按照[ππ)排序。考虑将第i条边作为始边,指针l指向逆时针方向的一个大于等于π2(第一个不满足(v[i] ^ v[l]) >= 0 && v[i] * v[l] > 0)的位置,指针r指向逆时针方的第一个大于3π2(第一个满足(v[i] ^ v[l]) < 0 && v[i] * v[l] > 0)的位置,则以第i条边作为始边构成的非锐角个数为rl;逆时针枚举每一条边,指针l,r是单调的,可以O(n)统计出非锐角个数。

时间复杂度的瓶颈来自n次极角排序,为O(n2logn)

代码

//
// Created by vv123 on 2022/7/13.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 2e4 + 10;
struct point {
    int x, y;
    point operator-(const point &p) const { return {x - p.x, y - p.y}; }
    int operator*(const point &p) const { return x * p.x + y * p.y; }
    int operator^(const point &p) const { return x * p.y - p.x * y; }
    int quad() const {
        if (y > 0 || (y == 0 && x >= 0)) return 1;
        return 2;
    }//下平面定义为[-180,0),上平面定义为[0,180)
} p[N], v[N];

bool cmp(const point &a,const point &b) {
    if (a.quad() != b.quad()) return a.quad() < b.quad();
    else return (a ^ b) > 0;
}

int n, R, D;

void work(int id) {
    point O = p[id];
    int m = 0;
    for (int i = 1; i <= n; i++) {
        if (i == id) continue;
        v[++m] = p[i] - O;
    }
    sort(v + 1, v + 1 + m, cmp);
    //for (int i = 1; i <= m; i++) cout << v[i].x << " " << v[i].y << "\n";

    int l = 1, r = 1;
    for (int i = 1; i <= m; i++) {
        while (l <= m && (v[i] ^ v[l]) >= 0 && v[i] * v[l] > 0) l++;
        while (r <= m && ((v[i] ^ v[r]) >= 0 || v[i] * v[r] <= 0)) r++;
        //cout << i << " " << l << " " << r << "\n";
        D += r - l;
    }
}

void solve() {
    D = 0;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> p[i].x >> p[i].y;
    }
    for (int i = 1; i <= n; i++) {
        work(i);
    }
    //cout << D << endl;
    R = n * (n - 1) * (n - 2) / 2 - D;
    cout << (R - D * 2) / 3 << "\n";
}
signed main() {
    int T = 1;
    //cin >> T;
    while (T--) {
        solve();
    }
    return 0;
}

H-洛水神女 水题

题意

n只猫,每只猫在生日之后有Pi的概率死亡,求他们的期望寿命之和 对109+7取模.

n5×105

解题思路

设一只猫每次生日后的死亡率为p,则它的寿命期望为

$\sum \limits_{i=1}{\infty}i(1-p)p =-p\sum \limits_{i=1}{\infty}[(1-p)i]'=-p[\sum \limits_{i=1}{\infty}(1-p)i]'=-p(\frac{1}{p}-1)'=\frac{1}{p} $

因此只需求出1Pi=ai1bi(mod109+7)

根据费马小定理,a1ap2(modp),p=109+7

代码

//
// Created by vv123 on 2022/7/12.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;

const int mod = 1e9 + 7;

typedef long long ll;

pair<signed, signed> nextPair(signed &x) {
    signed a, b;
    x ^= x << 13; x ^= x >> 17; x ^= x << 5; x %= 10000000; a = x;
    x ^= x << 13; x ^= x >> 17; x ^= x << 5; x %= 10000000; b = x;
    b = max(b, 1); a = max(a % b, 1);
    //cout << a << " " << b << "\n";
    return make_pair(a, b);
}

int pow(int a, int b, int p) {
    int res = 1;
    for (; b; b >>= 1) {
        if (b & 1) res = res * a % p;
        a = a * a % p;
    }
    return res;
}



void solve() {
    int n; signed x;
    cin >> n >> x;
    ll ans = 0;
    for (int i = 1; i <= n; i++) {
        pair<signed, signed> t = nextPair(x);
        ans = (ans + 1ll * t.second % mod * 1ll * pow(t.first, mod - 2, mod)) % mod;
    }
    cout << ans << "\n";
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T = 1;
    //cin >> T;
    while (T--) {
        solve();
    }
    return 0;
}

N-黑灰游戏 改 博弈

题意

n个石子堆,每堆有ai个石子。

奇数轮由Kate指定一堆石子,Emilico从中拿走至少一个石子。

偶数轮由Emilico指定一堆石子,Kate从中拿走至少一个石子。

拿走最后一个石子的一方获胜。问Emilico是否有必胜策略。

解题思路

我们将石子堆分为两类:

  • A类:只有一个石子

  • B类:多于一个石子

考虑最简单的情况:如果所有石子堆都只有一个石子(都是A类堆),显然当堆数为奇数时Emilico获胜。

考虑只有一个B类堆。

如果上一种情况的胜者指定了B类堆,上一种情况的败者一定会将它变成A类堆,改变胜负。

如果上一种情况的败者指定了B类堆,上一种情况的胜者一定会把它拿完,依然获胜。

不管哪种情况,都是指定了B类堆的一方失败。

因此,双方一定不会主动指定B类堆,不得不指定B类堆的一方必败

容易发现:A类堆个数为偶数时,B类堆一定会被Kate指定,从而Emilico获胜。否则Kate获胜。

下面使用数学归纳法证明:存在K(K ≥ 1)个B类堆时,结论都是"A类堆个数为偶数数时Emilico获胜"

首先K=1的情况已被证明。

假设当B类堆个数为K时,结论是"A类堆个数为偶数时Emilico获胜",则当B类堆个数为K+1时:

考虑哪一方指定了第一个B类堆。

如果B类堆个数为K时的胜者指定了第一个B类堆,B类堆个数为K时的败者一定会将它变成A类堆,使胜负与B类堆个数为K的情况相反,得以取胜。

如果B类堆个数为K时的败者指定了第一个B类堆,B类堆个数为K时的胜者者一定会将它取完,使胜负与B类堆个数为K的情况相同,得以取胜。

因此不管哪种情况,都是不得不指定第一个B类堆的一方失败,由Kate还是Emilico被迫指定第一个B类堆,只与A类堆的数量奇偶性有关。结论仍为"A类堆个数为偶数时Emilico获胜"。因此结论对任意正整数K都成立。

综上所述,只有奇数个A类堆,或者存在B类堆且A类堆个数为为偶数时,Emilico获胜。

代码

//
// Created by vv123 on 2022/7/20.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define seed 131
using namespace std;

const int mod = 1e9 + 7;
const int N = 2e6 + 10;
const int inf = 0x3f3f3f3f;


void solve() {
    int n, t, cntA = 0, cntB = 0;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> t;
        if (t == 1) cntA++;
        else cntB++;
    }
    if ((cntB == 0) ^ (cntA % 2 == 0)) puts("Win");
    else puts("Lose");
}


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

Q-挑战新高考 反演 杜教筛

题意

从L至R中的R-L+1个整数中随机取2个不同的数。求这2个数互质的概率。

1T10,1L,R109

解题思路

我们在课上学过

  • μ(n)={1n=1(1)|P|n=ipi0pi,pi2n

  • d|nμ(d)=[n=1]

  • i=1nj=1n[gcd(i,j)=1]=i=1nj=1nd|gcd(i,j)μ(d)=d=1nμ(d)nd2

同样地,我们考虑d的倍数在[L,R]中的出现次数,有i=LRj=LR[gcd(i,j)=1]=d=1Rμ(d)(RdL1d)2

(ppt上的结论有误)

注意到互质的(i,j)(1,1)外都可以互换,因此如果L=1,上述和式的结果是符合要求的排列数+1,否则恰好为所有可能的排列个数。

因此此题答案为i=LRj=LR[gcd(i,j)=1][L=1](RL+1)(RL)=d=1Rμ(d)(RdL1d)2[L=1](RL+1)(RL)

用数论分块进行计算的过程中需要用到莫比乌斯函数的109前缀和,可以使用杜教筛。

杜教筛被用来处理数论函数的前缀和问题。对于求解一个前缀和,杜教筛可以在低于线性时间的复杂度内求解。

对于数论函数 f,要求我们计算 S(n)=i=1nf(i).

我们想办法构造一个 S(n) 关于 S(ni) 的递推式

对于任意一个数论函数 g,必满足

i=1ndig(d)f(id)=i=1ng(i)S(ni)i=1n(fg)(i)=i=1ng(i)S(ni)

略证:

g(d)f(id) 就是对所有 in 的做贡献,因此变换枚举顺序,枚举 d,id(分别对应新的 i,j

i=1ndig(d)f(id)=i=1nj=1nig(i)f(j)=i=1ng(i)j=1nif(j)=i=1ng(i)S(ni)

那么可以得到递推式

g(1)S(n)=i=1n(fg)(i)i=2ng(i)S(ni)

那么假如我们可以快速对 i=1n(fg)(i) 求和,并用数论分块求解 i=2ng(i)S(ni) 就可以在较短时间内求得 g(1)S(n).

由狄利克雷卷积,我们知道:

ϵ=μ1ϵ(n)= [n=1]

ϵ(n)=dnμ(d)

S1(n)=i=1nϵ(i)i=2nS1(ni)

=1i=2nS1(ni)

观察到 ni 最多只有 O(n) 种取值,我们就可以应用数论分块来计算每一项的值了。

直接计算的时间复杂度为 O(n34)。考虑先线性筛预处理出前 n23 项,剩余部分的时间复杂度为

O(0n13nx dx)=O(n23)

对于较大的值,可以用 unordered_map 存下其对应的值,方便以后使用时直接使用之前计算的结果。

代码

//
// Created by vv123 on 2022/7/29.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;


int n, m, a, ans;
const int N = 1e7 + 10;

int min(int a, int b, int c) {
    return min(min(a, b), c);
}
int pow(int a, int b, int p) {
    int res = 1;
    for (; b; b >>= 1) {
        if (b & 1) res = res * a % p;
        a = a * a % p;
    }
    return res;
}

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

int vis[N], prime[N], mu[N], sum[N];

void Shai(int n = N - 1) {
    vis[1] = mu[1] = 1;
    int cnt = 0;
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) {
            prime[++cnt] = i;
            mu[i] = -1;
        }
        for (int j = 1; j <= cnt && i * prime[j] <= n; j++) {
            vis[i * prime[j]] = 1;
            if (i % prime[j] == 0) {
                mu[i * prime[j]] = 0; 
                break;
            } else {
                mu[i * prime[j]] = -mu[i];
            }
        }
    }
    for (int i = 1; i <= n; i++) {
        sum[i] = sum[i - 1] + mu[i];
    }
}

unordered_map<int, int> sum_mu;
inline int get_mu(int x) {
    if (x < N) return sum[x];
    if (sum_mu[x]) return sum_mu[x];
    int res = 1;
    for (int l = 2, r; l <= x; l = r + 1) {
        r = x / (x / l);
        res -= (r - l + 1) * get_mu(x / l);
    }
    return sum_mu[x] = res;
}


int f(int L, int R) {
    int res = 0;
    for (int l = 1, r; l <= L; l = r + 1) {
        r = min(L / (L / l), R / (R / l));
        int t = ((R / l) - (L / l)) * ((R / l) - (L / l));
        res += (get_mu(r) - get_mu(l - 1)) * t;
    }
    for (int l = L + 1, r; l <= R; l = r + 1) {
        r = R / (R / l);
        int t = (R / l) * (R / l);
        res += (get_mu(r) - get_mu(l - 1)) * t;
    }
    return res;
}

int bf(int L, int R) {
    int res = 0;
    for (int i = L; i <= R; i++)
        for (int j = L; j <= R; j++)
            if (gcd(i, j) == 1) res++;
    return res;
}

void solve() {
    int L, R;
    cin >> L >> R;
    int a = f(L-1, R) - (L == 1), b = (R - L + 1) * (R - L), d = gcd(a, b);
    cout << a / d << "/" << b / d << "\n";
}

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

S- For D. E. Knuth 拓展欧拉定理

题意

a↑↑b(modp)

1a,b,p109

解题思路

前置知识:拓展欧拉定理

ab{abmodφ(p),gcd(a,p)=1ab,gcd(a,p)1,b<φ(p)abmodφ(p)+φ(p),gcd(a,p)1,bφ(p)(modp)

由题意,a↑↑1=a,a↑↑b=aa↑↑(b1)

于是当a>1b充分大时,有

a↑↑b(modp)=aa↑↑(b1)(modp)

=aa↑↑(b1)modφ(p)+φ(p)(modp)

=aaa↑↑(b2)modφ(p)+φ(p)(modp)

=aaa↑↑(b2)modφ(φ(p))+φ(φ(p))modφ(p)+φ(p)(modp)

=

注意到欧拉函数的多次嵌套很快会收敛到φ(1)=1,因此可以递归计算。

我们设f(a,b,p)=(a↑↑b)(modp)

则有f(a,b,p){1a=1p>10p=1af(a,b1,φ(p))+φ(p)a>1,p>1,b>5b5

......

我们发现2↑↑5=265536是一个很大的数字

这可以确保a↑↑(b1)φ(p)a>1,b>5时一定成立.

并且由于aφ(p)1(modp),gcd(a,p)=1,因此拓展欧拉定理的第三条对gcd(a,p)=1也适用

因此采取这种方法计算,结果一定是正确的

代码

//
// Created by vv123 on 2022/7/10.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;

int a, b, p;

inline int phi(int x) {
    int res = x;
    for (int i = 2; i * i <= x; i++) {
        if (x % i == 0) {
            res = res / i * (i - 1);
            while (x % i == 0) x /= i;
        }
    }
    if (x > 1) res = res / x * (x - 1);
    return res;
}


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

int pow(int a, int b, int p) {
    if (gcd(a, p) == 1) b %= phi(p);
    if (gcd(a, p) != 1 && b >= phi(p)) b = b % phi(p) + phi(p);
    int res = 1;
    for (; b; b >>= 1) {
        if (b & 1) res = res * a % p;
        a = a * a % p;
    }
    return res;
}

int f(int a, int b, int p) {
    if (p == 1) return 0;
    else if (b > 5){
        int t = pow(a, f(a, b - 1, phi(p)) + phi(p), p);
        //printf("f(%d,%d,%d)=%d\n", a, b, p, t);
        return t;
    } else {
        int res = 1;
        for (int i = 1; i <= b; i++) {
            res = pow(a, res, p);
        }
        return res;
    }
}

void solve() {
    cin >> a >> b >> p;
    if (a == 1) {
        if (p == 1) puts("0");
        else cout << 1 << "\n";
        return;
    }
    cout << f(a, b, p)  << "\n";
}

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

W-奥日与风袭废墟 线性筛

题意

φ,μ,σ0,σ1,(μφ)σ1的前500000

解题思路

以上积性函数在质数的取值通常可以由定义得到。

而对于合数,线性筛保证了每次由最小的质因子筛掉。我们设合数n=p×np是它的最小质因子。则有以下两种情况

  1. nmodp0,则pn互质,根据积性函数的性质,f(n)=f(p)f(n)

  2. nmodp=0,则p也是n的最小质因子。

    n=p1k1p2k2...。其中p1是它的最小质因子。我们求一个辅助函数g(n)=f(p1k1)

    那么f(n)=f(n)g(n)g(n)

g(n)是很好求的:

  1. nmodp0,n被唯一的最小质因子p筛去,g(n)=f(p)

  2. nmodp=0,只需在g(n)的基础上添加一个p的贡献即可。

代码

//
// Created by vv123 on 2022/7/8.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 500000 + 10;
int n;
int vis[N], prime[N], phi[N], mu[N], c[N], d[N], g[N], f[N], F[N], G[N], gg[N];

void Shai() {
    vis[1] = phi[1] = mu[1] = d[1] = g[1] = f[1] = G[1] = gg[1] = F[1] = 1;
    int cnt = 0;
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) {
            prime[++cnt] = i;
            phi[i] = i - 1;
            mu[i] = -1;
            c[i] = 1; //质数的最小质因子指数为1
            d[i] = c[i] + 1;   //质数的约数个数为2。d[n] = PI(c_i + 1)
            g[i] = i + 1;//g[i]为i的最小质因子0~k次方和,约数和f[i]为所有k次质因子g[i]的积
            gg[i] = i;
            f[i] = g[i];
            G[i] = mu[1] * phi[1] * f[i] + mu[i] * phi[i] * f[1];
            F[i] = G[i];
        }
        for (int j = 1; j <= cnt && i * prime[j] <= n; j++) {
            //printf("i=%d prime[j]=%d\n", i, prime[j]);
            vis[i * prime[j]] = 1;
            if (i % prime[j] == 0) {
                //printf("%d mod %d == 0\n", i, prime[j]);
                phi[i * prime[j]] = prime[j] * phi[i];  //n'包含了n的所有质因子,与n的欧拉函数只差p1
                mu[i * prime[j]] = 0; //p1的指数大于1,莫比乌斯函数为0
                c[i * prime[j]] = c[i] + 1; //注意到i和i * prime[j]的最小质因子都是prime[j],后面都很显然了
                d[i * prime[j]] = d[i] / (c[i] + 1) * (c[i * prime[j]] + 1);
                g[i * prime[j]] = g[i] * prime[j] + 1;
                gg[i * prime[j]] = gg[i] * prime[j];
                f[i * prime[j]] = f[i] / g[i] * g[i * prime[j]];
                G[i * prime[j]] = G[i] + gg[i];         //
                F[i * prime[j]] = F[i] / G[i] * G[i * prime[j]];

                break;
            } else {
                //printf("%d mod %d == %d\n", i, prime[j], i % prime[j]);
                phi[i * prime[j]] = phi[prime[j]] * phi[i]; //最小质因子不整除其他部分,n'和p1互质
                mu[i * prime[j]] = mu[prime[j]] * mu[i];
                c[i * prime[j]] = 1;
                d[i * prime[j]] = d[prime[j]] * d[i];
                g[i * prime[j]] = 1 + prime[j];
                gg[i * prime[j]] = prime[j];
                f[i * prime[j]] = f[prime[j]] * f[i];
                G[i * prime[j]] = G[prime[j]];
                F[i * prime[j]] = F[prime[j]] * F[i];
            }
        }
    }
}
 
void solve() {
    cin >> n;
    Shai();
    for (int i = 1; i <= n; i++) printf("%d ", phi[i]); puts("");
    for (int i = 1; i <= n; i++) printf("%d ", mu[i]); puts("");
    for (int i = 1; i <= n; i++) printf("%d ", d[i]); puts("");
    for (int i = 1; i <= n; i++) printf("%d ", f[i]); puts("");
    //for (int i = 1; i <= n; i++) printf("%d ", gg[i]); puts("");
    //for (int i = 1; i <= n; i++) printf("%d ", G[i]); puts("");
    for (int i = 1; i <= n; i++) printf("%d ", F[i]); puts("");
}

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

X-多项式乘法 FFT

题意

给出两个多项式的系数,求两个多项式乘积的系数

0n,m105,09

解题思路

FFT是这样一种东西:通过由分治实现的DFT,我们可以O(nlogn)将一个多项式从系数表示法转化成点值表示法。已知两个多项式的点值表示,则它们的乘积的点值表示就是点值的乘积。我们得到乘积的点值表示后,可以经过 IDFT(傅里叶反变换)的作用转换成系数形式 。

代码

//
// Created by vv123 on 2022/7/11.
//
#include <bits/stdc++.h>
#define int long long
#define pi M_PI
using namespace std;
typedef complex<double> cp;
const cp I(0, 1);
const int N = 5e6 + 10;

cp t[N], f[N], g[N], h[N];
int a[N], b[N];

void fft(cp *f, int n, int rev) {
    if (n == 1) return;
    for (int i = 0; i < n; i++) t[i] = f[i];
    for (int i = 0; i < n; i++) {
        if (i % 2) f[n / 2 + i / 2] = t[i];
        else f[i / 2] = t[i];
    }
    cp *g = f, *h = f + n / 2;
    fft(g, n / 2, rev); fft(h, n / 2, rev);
    cp cur(1, 0), step = exp(I * (2 * pi / n * rev));
    for (int k = 0; k < n / 2; k++) {
        t[k] = g[k] + cur * h[k];
        t[k + n / 2] = g[k] - cur * h[k];
        cur *= step;
    }
    for (int i = 0; i < n; i++) f[i] = t[i];
}



void solve() {
    int n, m;
    cin >> n >> m;
    for (int i = 0; i <= n; i++) {
        int x;
        cin >> x;
        f[i].real(x);
    }
    for (int i = 0; i <= m; i++) {
        int x;
        cin >> x;
        g[i].real(x);
    }
    int k = 1;
    while (k <= n + m) k <<= 1;
    fft(f, k, 1); fft(g, k, 1);
    for (int i = 0; i <= k; i++)
        h[i] = f[i] * g[i];
    fft(h, k, -1);
    for (int i = 0; i <= n + m; i++)
        cout << (int) ((h[i].real() / k) + 0.5)  << " ";
}



signed main() {
    int T = 1;
    //cin >> T;
    while (T--) {
        solve();
    }
    return 0;
}
posted @   _vv123  阅读(98)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示