AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树)

补题链接:Here

A - Plural Form

字符串,末尾有 s 的加es,不然加 s .

B - Go to Jail

输入的时候判断一下是否连续相等即可

C - A x B + C (math,欧拉筛)

题意:请问能找到多少组 (A,B,C)= N .

思路一:对于使得 (A imes B < N)(A,B) 都会存在唯一值 C 所以我们只需要对 (A,B) 计数即可

(A) 值确定时,(B)(⌊frac{N -1}{A}⌋) 。所以我们遍历 (1) ~ (N - 1) 即可。

AC 代码

int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    int n;
    cin >> n;
    ll cnt = 0;
    for (int i = 1; i <= n; ++i) cnt += (n - 1) / i;
    cout << cnt << "
";
    return 0;
}

思路二:

另外在 CP wiki上也解释了这道题可以计算 (N - C) 的因子数来解决

固定 (C),符合条件的二元组 ((A,B)) 的数目就等于 (N−C) 的因子数,而一个正整数的因子数可以通过质因数分解求得。

设:(n = p_1^{a1}p_2^{a2}...p_n^{an}),则其因子数为 (∏_{i = 1}^n(a_i + 1))

所以我们就计算 (1dots N-1) 每个数的因子数,然后求和即可。

因为([1,x])范围内的质数个数近似为 (frac{x}{ln x}),总时间复杂度为 (O(frac{Nsqrt{N}}{log N}))

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using namespace std;
int main() {
    int n;
    cin >> n;
    vector<int> primes = {2, 3, 5, 7, 11, 13};
    for (int i = 17; i <= n - 1; i += 2) {
        bool is_prime = true;
        for (int j = 0; primes[j] * primes[j] <= i; ++j) {
            if (i % primes[j] == 0) {
                is_prime = false;
                break;
            }
        }
        if (is_prime)
            primes.emplace_back(i);
    }
    int ans = 0;
    for (int i = 1; i < n; ++i) {
        int k   = i;
        int fac = 1;
        for (int j = 0; primes[j] * primes[j] <= k; ++j) {
            if (k % primes[j] == 0) {
                int cnt = 0;
                while (k % primes[j] == 0)
                    cnt++, k /= primes[j];
                fac *= (cnt + 1);
            }
        }
        if (k != 1) fac *= 2;
        ans += fac;
    }
    cout << ans;
}

思路三:

欧拉筛,令f[i]为a*b=i的方案数,
f[i]的前缀和就是a*b<=i的方案数.

又因为题目中a*b+c的c必须>=1,那么我们输出f[n-1]即可,原理是留了一个1给c.

AC 代码

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const ll maxn = 2e6 + 7;
ll f[maxn], n;
signed main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    for (int i = 1; i < maxn; ++i)
        for (int j = i; j < maxn; j += i) f[j] += 1;
    for (int i = 1; i < maxn; ++i) f[i] += f[i - 1];
    cin >> n;
    cout << f[n - 1] << "
";
    return 0;
}

D - Leaping Tak

题意:

(N) 个房间排成一行,房间编号为 (1,2,...,N)。目前高桥所在房间为 (1) ,现在他想移动到 (N) 号房间。

给定 (K) 个不相交的区间,而集合 (S) 是这 (K) 个区间的并集。移动规则如下:

  • 高桥在房间 (i),可以从集合 (S) 中选择一个整数 (d),这样可以移动到房间 (i+d)
  • 高桥不可以移动到房间以外。

思路:

考虑我们当前处于 (x) 位置,我们上一步可能来自哪里?

我们需要检查所有的区间,然后找出对应的区间 ([l,r])(如果这样的区间存在的话),这一区间包含了所有能够利用原区间中的步长跳跃到当前位置的位置,也即上一步可能的位置。接下来我们将 (sum(l,r))累加到 (dp[x])上。

因为我们需要快速计算 (sum(l,r)),所以实际上我们可以使用前缀和 (sum[i]),而非 (dp[i])

  • (mathcal{O}(NK))
#include <bits/stdc++.h>
using ll = long long;
using namespace std;
const ll mod = 998244353;
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    int n, k;
    cin >> n >> k;
    vector<pair<int, int>> v(k);
    for (int i = 0; i < k; ++i) cin >> v[i].first >> v[i].second;
    vector<ll> sum(n + 1, 0);
    sum[1] = 1;
    for (int i = 2; i <= n; ++i) {
        ll cur = 0;
        for (int j = 0; j < k; ++j) {
            // 如果这个区间的起点大于 i 就不可能 i 是由这个点移动到的了
            if (v[j].first >= i) continue;
            int l = max(1, i - v[j].second);
            int r = i - v[j].first;
            cur += sum[r] - sum[l - 1];
        }
        cur %= mod;
        sum[i] = (sum[i - 1] + cur + mod) % mod;
    }
    cout << (sum[n] - sum[n - 1] + mod) % mod;
    return 0;
}

E - Sequence Sum

显然,操作足够多次后,一定会开始循环。因为 (Mleq10^5),所以循环的长度不超过 (10^5),因此我们直接模拟找出循环节即可。

注意:循环节不一定是 x 为起点

  • (mathcal{O}(M))
#include <bits/stdc++.h>
using ll = long long;
using namespace std;
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    ll n, x, m;
    cin >> n >> x >> m;
    map<ll, ll> mp;
    int idx = 0;
    vector<ll> path;
    do {
        mp[x] = idx++;
        path.emplace_back(x);
        x = x * x % m;
    } while (!mp.count(x));
    int start = mp[x];
    int len   = idx - start;
    ll sum    = 0;
    // 循环节之和
    for (int i = start; i < idx; ++i) sum += path[i];
    ll ans = 0;
    if (n < start) // 可能未进入循环节就结束累加了
        for (int i = 0; i < n; ++i) ans += path[i];
    else {
        for (int i = 0; i < start; ++i) ans += path[i];
        ll loop = (n - start) / len;
        ans += loop * sum;
        ll res = (n - start) % len; // 多余一节
        for (int i = 0; i < res; ++i) ans += path[start + i];
    }
    cout << ans << "
";
    return 0;
}

F - Simplified Reversi

当我们放一个白石头的时候,会改变多少黑石头的颜色?

对于第一种操作,这是由该列最上面的白石头决定的;对于第二种操作,这是由该行最左边的白石头决定的。

而每种操作的影响是什么呢?第一种操作会影响 (1dots X) 行( (X) 是操作的列最上面的白石头所在的行),而第二种操作会影响 (1dots X)列((X)是操作的行最左边的白石头所在的列)。

很自然地想到用线段树来处理。一个储存每行最上面的白石头,另一个储存每列最左边的白石头。对于非叶结点,我们存储区间的最大值。因为我们每次操作相当于一个切割,把所有大于XX的数都变为XX,因此,存储区间最大值,可以让我们很方便地判断当前的切割是否产生了影响。

  • (mathcal{O}(QlogN)).
#include <bits/stdc++.h>
using ll = long long;
using namespace std;
#define MAXN 200005
#define lson (idx << 1)
#define rson (idx << 1 | 1)

using namespace std;
typedef long long ll;

struct Node {
    int l, r, hi, lazy = 0;
};

struct SegTree {
    Node s[MAXN << 2];
    void calc(int idx) { s[idx].hi = max(s[lson].hi, s[rson].hi); }

    void pushdown(int idx) {
        if (s[idx].lazy) {
            for (int i = lson; i <= rson; ++i) {
                if (s[i].hi > s[idx].lazy) {
                    s[i].hi   = s[idx].lazy;
                    s[i].lazy = s[idx].lazy;
                }
            }
        }
        s[idx].lazy = 0;
    }

    void build(int idx, int l, int r, vector<int> &a) {
        s[idx].l = l, s[idx].r = r;
        if (l == r) {
            s[idx].hi = a[l];
            return;
        }
        int mid = (l + r) >> 1;
        build(lson, l, mid, a);
        build(rson, mid + 1, r, a);
        calc(idx);
    }

    void update(int idx, int l, int r, int x) {
        if (s[idx].hi <= x)
            return;
        if (s[idx].l >= l && s[idx].r <= r) {
            s[idx].hi   = x;
            s[idx].lazy = x;
            return;
        }
        pushdown(idx);
        int mid = (s[idx].l + s[idx].r) >> 1;
        if (l <= mid)
            update(lson, l, r, x);
        if (mid + 1 <= r)
            update(rson, l, r, x);
        calc(idx);
    }

    int query(int idx, int l, int r) {
        if (s[idx].l >= l && s[idx].r <= r)
            return s[idx].hi;
        pushdown(idx);
        int ans = 0;
        int mid = (s[idx].l + s[idx].r) >> 1;
        if (l <= mid)
            ans = max(ans, query(lson, l, r));
        if (mid + 1 <= r)
            ans = max(ans, query(rson, l, r));
        return ans;
    }
};

int main() {
    int n, q;
    cin >> n >> q;
    vector<int> col(n + 1, n), row(n + 1, n);
    col[n] = 1, row[n] = 1;
    SegTree C, R;
    C.build(1, 1, n, col);
    R.build(1, 1, n, row);
    ll ans = (ll)(n - 2) * (n - 2);
    for (int i = 0; i < q; ++i) {
        int t, x;
        cin >> t >> x;
        if (t == 1) {
            int hi = C.query(1, x, x);
            ans -= hi - 2;
            R.update(1, 1, hi, x);
            C.update(1, x, x, 1);
        } else {
            int hi = R.query(1, x, x);
            ans -= hi - 2;
            C.update(1, 1, hi, x);
            R.update(1, x, x, 1);
        }
    }
    cout << ans;
}

The desire of his soul is the prophecy of his fate
你灵魂的欲望,是你命运的先知。

原文地址:https://www.cnblogs.com/RioTian/p/14622790.html