P3846:「TJOI2007」可爱的质数题解

BSGS 算法模板题

有\(a^{x} \equiv b (mod \; p)\),求\(x\)的值。我们可以来推下这个式子,首先,这个\(x \in [0, p]\),我们可以设\(x=im-j\),其中\(m=\lceil \sqrt{p} \rceil\)。显然,\(j \in [0, m], i \in [1, m]\)。式子变成这个样子:

\[a^{im}\equiv a^j b (mod \; p)\]

然后我们只需要求出后半部分,压入 map 中,再枚举前半部分即可求出。

代码

// P3846.cpp
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll p, a, b;
ll quick_power(ll base, ll tim)
{
    ll tmp = 1;
    while (tim)
    {
        if (tim & 1)
            tmp = tmp * base % p;
        base = base * base % p;
        tim >>= 1;
    }
    return tmp;
}
map<ll, ll> hashTable;
int main()
{
    scanf("%lld%lld%lld", &p, &a, &b);
    a %= p;
    if (a == 0)
    {
        if (b == 0)
            printf("0");
        else
            printf("no solution");
        return 0;
    }
    hashTable.clear();
    ll m = ceil(sqrt(p));
    for (int j = 0, x = 1; j <= m; j++, x = x * a % p)
        hashTable[x * b % p] = j;
    ll unit = quick_power(a, m);
    for (int i = 1, x = unit; i <= m; i++, x = x * unit % p)
        if (hashTable.count(x))
        {
            printf("%lld", i * m - hashTable[x]);
            return 0;
        }
    printf("no solution");
    return 0;
}

P3773:「CTSC2017」吉夫特题解

神仙思路

有一个“显然”的定理:如果满足\(n\&k==k\),那么\(C^k_n\)为奇数。具体感性证明:litble 的证明。所以,直接上代码。

代码

// P3773.cpp
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int MAX_N = 233393, mod = 1000000007;
int n, arr[MAX_N], bucket[MAX_N], f[MAX_N], ans;
int getMod(int num) { return num >= mod ? num - mod : num; }
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d", &arr[i]), bucket[arr[i]] = i;
    for (int i = n; i >= 1; i--)
    {
        f[i] = 1;
        for (int j = (arr[i] & (arr[i] - 1)); j; j = arr[i] & (j - 1))
            if (bucket[j] > i)
                f[i] = getMod(f[i] + f[bucket[j]]);
        ans = getMod(ans + f[i]);
    }
    ans = (ans - n + mod) % mod;
    printf("%d", ans);
    return 0;
}

P1069:细胞分裂题解

思路转化

题目大意是这样的:给出数字\(N\),\(m_1\),\(m_2\),\(S_{i[1\dots N]}\),求最小的\(t\),使得某一\(S_i^t \; mod\; m_1^{m_2}\)。

所以之后就非常的显然了,我们只要分析出每一个数的质因数与\(m_1\)的质因数的关系即可。如果有一个数\(c\)的质因数集为\(A\),那么我们可以遍历\(m_1\)的质因数集,记录答案。如果质因数集不包含\(m_1\)的质因数集直接退出。

代码

// P1069.cpp
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#define ll long long
using namespace std;
const int MX_N = 30020, INF = 0x3f3f3f3f;
ll n, m1, m2, arr[MX_N], pipePrime[MX_N], cellPrime[MX_N], lit, ans = INF;
int main()
{
    scanf("%lld%lld%lld", &n, &m1, &m2);
    for (int i = 1; i <= n; i++)
        scanf("%lld", &arr[i]);
    if (m1 == 1)
    {
        printf("0");
        return 0;
    }
    for (int i = 2; m1 != 1; i++)
    {
        while (!(m1 % i))
            m1 /= i, pipePrime[i] += m2;
        lit = max(lit, (ll)i);
    }
    for (int i = 1; i <= n; i++)
    {
        ll now = 0;
        for (int j = 2; j <= lit; j++)
            if (pipePrime[j] != 0)
            {
                ll tim = 0;
                while (!(arr[i] % j))
                    arr[i] /= j, tim++;
                if (!tim)
                {
                    now = INF;
                    break;
                }
                now = max(now, (pipePrime[j] - 1) / tim);
            }
        ans = min(ans, now);
    }
    if (ans != INF)
        printf("%lld", ans + 1);
    else
        printf("-1");
    return 0;
}

 

CH3101:阶乘分解题解

数学思路

最近要开始学一些数学方面的东西了,这题作为基础题再好不过了。首先,这道题问的就是在阶乘\(N!=1*2*3*\dots *N\)中分解质因数。我们可以先求出\([1,N]\)范围内的质数,然后再来求出这个式子中质因数的次数。

memset(isPrime, true, sizeof(isPrime));
isPrime[1] = false;
for (int i = 2; i <= n; i++)
    if (isPrime[i])
    {
        primes.push_back(i);
        for (int factor = 2; factor * i <= n; factor++)
            isPrime[i * factor] = false;
    }

接下来,我们可以说明,在阶乘式中,对于一个素数\(p\),它的一次项出现次数为\( \lfloor \frac {N} {p} \rfloor \),则二次项出现的次数为\(\lfloor \frac {N} {p^2} \rfloor\),归纳起来,可以得到:

\[ \lfloor \frac {N} {p} \rfloor + \lfloor \frac {N} {p^2} \rfloor + \lfloor \frac {N} {p^3} \rfloor + \dots + + \lfloor \frac {N} {p^{log_{p} N}} \rfloor\]

化为循环式,得到:

\[ \sum_{p^i \leq N}^{i = 1} \; \lfloor \frac {N} {p^i} \rfloor\]

详细看代码吧:

// CH3101.cpp
#include <iostream>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <vector>
#define uint unsigned int
using namespace std;
const int MX_N = 5e6 + 100;
bool isPrime[MX_N];
uint n, cs[MX_N];
vector<uint> primes;
int main()
{
    scanf("%u", &n);
    memset(isPrime, true, sizeof(isPrime));
    isPrime[1] = false;
    for (int i = 2; i <= n; i++)
        if (isPrime[i])
        {
            primes.push_back(i);
            for (int factor = 2; factor * i <= n; factor++)
                isPrime[i * factor] = false;
        }
    int siz = primes.size();
    for (int i = 0; i < siz; i++)
    {
        uint c = 0;
        for (int num = n; num; num /= primes[i])
            c += num / primes[i];
        printf("%u %u\n", primes[i], c);
    }
    return 0;
}

数学期望和其线性性质

背景

自从NOIp 2018初赛第一次知道有数学期望这个东西起,我就一直很难理解。我一直没有去仔细的了解数学期望,而今天打了一场比赛,被逼着去了解了,现在我来写点东西记录一下这个概念。

概念介绍

\(数学期望\)是一个跟概率相关的概念。他的定义是:

数学期望是一个有限的事件的结果乘上一个事件的概率。

我们可以通过举例子来解释这个概念。比如取一个随机数(1-10之间),那么取到5的数学期望就是\(\frac {1} {2}\)。一般,我们用\(\mathbb{E}\)来表示这个值。

性质介绍

在本篇文章中,我只介绍一个性质。未来如果我学到了一些关于数学期望的新东西,那么我会把这些内容写在这篇文章里的。

数学期望在OI中比较重要的性质就是其线性性。简单来讲,如果有两个事件的数学期望为\(\mathbb{E}(a)\)和\(\mathbb{E}(b)\),那么这两个事件叠加起来就是\(\mathbb{E}(a) + \mathbb{E}(b) = \mathbb{E}(a+b)\)。这个性质在OI中比较重要,一些题目会利用这个性质让解题者使用递推的方式解出答案。接下来我们看一道题目:

例题

链接:T2062:随机数生成器

这道题目是我在华南师范大学附属中学举办的比赛中遇到的题目。虽然我这一题并没有AC,但是不妨碍我们来解释数学期望的一些性质和递推得解的方法。

我们来思考这样一个方程,设置\(\mathbb{E}(i)\)为输入为\(i\)的数学期望,那么,根据数学期望的线性性质,我们可以很容易推出以下方程:\[\mathbb{E}(i) =\frac {\mathbb{E}(1) + 1}{i} + \frac {\mathbb{E}(2) + 1}{i} + \frac {\mathbb{E}(3) + 1}{i} + \frac {\mathbb{E}(4) + 1}{i} + \dots + \frac {\mathbb{E}(i) + 1}{i}\]

而在本题中,可知\(\mathbb{E}(1) = 0\)。那么我们来变换一下式子,变成:\[\mathbb{E}(i) = \frac {\mathbb{E}(1) + \mathbb{E}(2) + \dots + \mathbb{E}(i – 1) + i}{n – 1}\]这样我们就可以递推出式子。

数学期望是OI中常见的概念,掌握它很有帮助。建议读者可以自己去阅读相关的资料,我在这里便不再描述。

P1351:联合权值题解

思路

这道题本来用爆搜来写的(DFS),然而TLE了3个点。我只好去向机房巨佬lornd求教。在他神奇的操作之下这道题从\(O(n^3)\)变成了\(O(n)\)的时间复杂度。

我们先来解析一下我们的流程。在存图之后,我们需要遍历n个点,然后获取与点n相连的点。这些点之间的距离必定为2。接下来我们来处理。

第一问

我们在遍历的时候记录下第一大和第二大的数,在处理完这些点之后,我们便把第一大、第二大的数相乘,可以得到这些有序对之中最大的联合权值,与ans变量取最大值便可解决第一问。

第二问

我们先来写一个公式:\[(\sum^{n}_{i=0}{a_i})^2 = \sum^{n}_{i=0}{a^2_i} + 2(\sum^{m}_{i=0,j=0}{a_i a_j})\]

第二问其实问的就是这个式子中的第三项:\[2(\sum^{m}_{i=0,j=0}{a_i a_j})\]

即\(2ab + 2cd + 2ef + \dots \)所以我们如果要求,我们便可以在代码中把第一项和第二项算出,相减即可出答案。

代码

// P1351.cpp
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>

using namespace std;

#define ull unsigned long long
const int maxn = 2000000;
int n;
ull W[maxn];
// The graph data structure;
int to[maxn];
int point[maxn];
int next[maxn];
bool vis[maxn];
int current = 0;
// answers;
ull ans_max = 0;
ull ans_tot = 0;
// initialize to make the graph up;
void init()
{
    memset(to, -1, sizeof(to));
    memset(point, -1, sizeof(point));
    memset(next, -1, sizeof(next));
    memset(vis, false, sizeof(vis));
    cin >> n;

    for (int i = 0; i < n - 1; i++)
    {
        int x, y;
        cin >> x >> y;
        next[current] = point[x];
        to[current] = y;
        point[x] = current;
        current++;
        next[current] = point[y];
        to[current] = x;
        point[y] = current;
        current++;
    }
    for (int i = 1; i <= n; i++)
        cin >> W[i];
}
// calculate the n^2;
ull secondary(ull a)
{
    return a * a;
}
// solve;
void solve()
{
    for (int i = 1; i <= n; i++)
    {
        // get the maximum and the second one;
        ull firmax = 0;
        ull secmax = 0;
        // the term1 and the term2;
        ull tmp1 = 0;
        ull tmp2 = 0;
        for (int j = point[i]; j != -1; j = next[j])
        {
            int jto = to[j];
            if (W[jto] > firmax)
                secmax = firmax, firmax = W[jto];
            else if (W[jto] > secmax)
                secmax = W[jto];
            tmp1 += W[jto];
            tmp2 += secondary(W[jto]);
        }
        // add them up;
        ans_tot += (secondary(tmp1) - tmp2) % 10007;
        ans_tot %= 10007;
        ans_max = max(ans_max, firmax * secmax);
    }
}
// just solve it;
int main()
{
    init();
    solve();
    cout << ans_max << " " << ans_tot;
    return 0;
}

lornd tql%%%