牛牛与整除分块

化简可得

于是以为界,在左边或者右边找对应的位置即可。

#include <bits/stdc++.h>
#define sc(x) scanf("%lld", &(x))
#define pr(x) printf("%lld\n", (x))
#define rep(i, l, r) for (int i = (l); i <= (r); ++i)
using namespace std;
typedef long long ll;
const int N = 1e5 + 7;
const int mod = 1e9 + 7;
int main() {
    ll T, n, x;
    sc(T);
    while (T--) {
        sc(n), sc(x);
        ll a = sqrt(n);
        if (x <= a)
            pr(x);
        else {
            ll l = n / a;
            ll ans = 0;
            if (a != l) ++ans;
            ll now = n / x;
            ans += a - now + a;
            pr(ans);
        }
    }
    return 0;
}

牛牛与交换排序

简单分析后可知

  • 第一次操作必然使最小的、不在其位的数让它归位
  • 那么长度就已经固定了
  • 那么接下来要做的就是模拟,看能否完成排序即可

可以用deque双端队列模拟,也可以用平衡树

#include <bits/stdc++.h>
#define sc(x) scanf("%d", &(x))
#define pr(x) printf("%lld\n", (x))
#define rep(i, l, r) for (int i = (l); i <= (r); ++i)
using namespace std;
typedef long long ll;
const int N = 1e5 + 7;
const int mod = 1e9 + 7;
int n, a[N];
deque<int> q;

int main() {
    sc(n);
    rep(i, 1, n) sc(a[i]);
    int x = 0;  //第一个不在正确位置上的数
    // aka 不在正确位置上的 最小的数
    rep(i, 1, n) if (a[i] != i) {
        x = i;
        break;
    }
    if (!x) return puts("yes\n1"), 0;  //当前已经有序
    int now, len;
    rep(i, x + 1, n) if (a[i] == x) {
        now = i;          // 这个最小的错位数 现在在now这个位置
        len = i - x + 1;  // 需要翻转的线段长度
        break;
    }
    rep(i, x, now - 1) q.emplace_front(a[i]);
    bool tag = 0;
    for (int i = x; i + len - 1 <= n; i++) {
        if (!tag)
            q.emplace_front(a[i + len - 1]);
        else
            q.emplace_back(a[i + len - 1]);
        if (tag) {
            if (q.front() != i) tag = 0;
        } else {
            if (q.back() != i) tag = 1;
        }
        if (tag) {
            if (q.front() != i) return puts("no"), 0;
            q.pop_front();
        } else {
            if (q.back() != i) return puts("no"), 0;
            q.pop_back();
        }
    }
    for (int i = n - len + 2; i <= n; i++) {
        if (tag) {
            if (q.front() != i) return puts("no"), 0;
            q.pop_front();
        } else {
            if (q.back() != i) return puts("no"), 0;
            q.pop_back();
        }
    }
    printf("yes\n%d", len);
    return 0;
}

牛牛与比赛颁奖

本题其实是一道非常基础的离散化+差分的板子题。

#include <bits/stdc++.h>
#define sc(x) scanf("%d", &(x))
#define pr(x) printf("%d ", (x))
#define rep(i, l, r) for (int i = (l); i <= (r); ++i)
using namespace std;
typedef long long ll;
const int N = 1e5 + 7;
const int mod = 1e9 + 7;
typedef pair<int, int> pii;
pii a[N];
vector<int> ori;              // 存储原值
unordered_map<int, int> pos;  // key:原值 val: 离散化后
int d[N * 2];                 // 差分数组
int num[N];                   // num[x]表示过x题的队伍数目
int main() {
    int n, m;
    sc(n), sc(m);
    rep(i, 1, m) sc(a[i].first), sc(a[i].second);
    // 离散化
    rep(i, 1, m) {
        ori.emplace_back(a[i].first);
        ori.emplace_back(a[i].second + 1);
    }
    sort(ori.begin(), ori.end());
    ori.erase(unique(ori.begin(), ori.end()), ori.end());
    int sz = ori.size();
    for (int i = 0; i < sz; ++i) pos[ori[i]] = i;
    // 差分
    rep(i, 1, m) {
        d[pos[a[i].first]]++;
        d[pos[a[i].second + 1]]--;
    }
    // 前缀和
    for (int i = 1; i < sz; ++i) d[i] += d[i - 1];
    // 统计过x题的人数
    --sz;
    int maxAcNum = 0;
    for (int i = 0; i < sz; ++i) {
        int acNum = d[i];                   //该区间过题数目
        maxAcNum = max(maxAcNum, acNum);    //更新最多过题数
        int teamNum = ori[i + 1] - ori[i];  //该区间队伍数目
        num[acNum] += teamNum;
    }
    // 计算名额
    int au = (n + 10 - 1) / 10;
    int ag = (n + 4 - 1) / 4;
    int cu = (n + 2 - 1) / 2;
    // 计算答案
    int ans1 = 0, ans2 = 0, ans3 = 0;
    for (int i = maxAcNum; i; --i)  // 要求至少过1题
        if (ans1 < au)
            ans1 += num[i];
        else if (ans1 + ans2 < ag)
            ans2 += num[i];
        else if (ans1 + ans2 + ans3 < cu)
            ans3 += num[i];
    cout << ans1 << ' ' << ans2 << ' ' << ans3;
    return 0;
}

更优雅的做法是直接查询差分的map,这样就省去了离散化的过程,码量很小。

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int g[N];
map<int, int> mp;
int num[5];
int main() {
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1, x, y; i <= m; ++i) {
        scanf("%d%d", &x, &y);
        mp[x]++, mp[y + 1]--;
    }
    num[1] = (n + 10 - 1) / 10;
    num[2] = (n + 4 - 1) / 4;
    num[3] = (n + 2 - 1) / 2;
    int now = 0, pre = 1, maxn = 0;
    for (auto &i : mp) {  // 遍历差分map
        maxn = max(maxn, now);
        g[now] += i.first - pre;  // 前后位置差值就是这个区间的人数
        now += i.second;  // 加上差分量 差分量就是过题数的位移量
        pre = i.first;
    }
    int ans1 = 0, ans2 = 0, ans3 = 0;
    for (int i = maxn; i; --i)  // 要求至少过1题
        if (ans1 < num[1])
            ans1 += g[i];
        else if (ans1 + ans2 < num[2])
            ans2 += g[i];
        else if (ans1 + ans2 + ans3 < num[3])
            ans3 += g[i];
    cout << ans1 << ' ' << ans2 << ' ' << ans3;
    return 0;
}

牛牛的“质因数”

埃筛做法

首先什么是埃筛:

#include <stdio.h>
#include <string.h>
const int N = 100 + 8;
int isPrime[N];
void sieve() {
    memset(isPrime, -1, sizeof isPrime);
    isPrime[0] = isPrime[1] = 0;
    for (int i = 2; i < N; ++i)
        if (isPrime[i])
            for (int j = 2; i * j < N; ++j) isPrime[i * j] = 0;
}
int main() {
    sieve();
    for (int i = 2; i < N; ++i)
        if (isPrime[i]) printf("%d ", i);
    return 0;
}

可以发现,在朴素的埃筛里,对于每一个合数,它会被每个它的质因数筛到。而如果是质数,他的质因数合并就是它本身。

所以只需要实时更新下每个数的质因数合并即可。

#include <bits/stdc++.h>
#define sc(x) scanf("%lld", &(x))
#define pr(x) printf("%lld\n", (x))
#define rep(i, l, r) for (int i = l; i <= r; ++i)
using namespace std;
typedef long long ll;
const int N = 4e6 + 7;
const int mod = 1e9 + 7;
bitset<N> isPrime;
ll ans[N];
int main() {
    ll w[10] = {1};
    rep(i, 1, 9) w[i] = w[i - 1] * 10;
    ll n;
    sc(n);
    isPrime.set();
    rep(i, 2, n) {
        if (isPrime[i]) {
            ans[i] = i;
            int len = log10(i) + 1;
            for (int j = 2; i * j <= n; ++j) {
                int now = i * j;
                isPrime[now] = 0;
                do {
                    ans[i * j] = (ans[i * j] * w[len] % mod + i) % mod;
                    now /= i;
                } while (now % i == 0);
            }
        }
    }
    ll res = 0;
    rep(i, 2, n) res = (res + ans[i]) % mod;
    pr(res);
    return 0;
}

这种做法时间复杂度本题300ms


欧拉筛 + DFS

欧拉筛是近似线性的筛法,这意味着不能像埃筛一样,对每个数更新答案。

但是可以从另一个角度出发:

  • 对于每个质数,它的质因数合并就是它本身。
  • 对于每个合数,它的质因数合并是它所有质因数的拼接。
  • 反过来说,可以用所有的质因数乘出来所有的数,对于质因数合并,也就是拼接。
  • 所以每个数和它的质因数合并都是一一对应的。

所以,可以直接去尝试拼接就可以了,如果是在所求范围内的,就加上权重即可,这也是唯一分解定理的逆向应用,非常巧妙。

#include <bits/stdc++.h>
#define sc(x) scanf("%lld", &(x))
#define pr(x) printf("%lld\n", (x))
#define rep(i, l, r) for (int i = l; i <= r; ++i)
using namespace std;
typedef long long ll;
const int N = 4e6 + 7;
const int mod = 1e9 + 7;
bitset<N> isPrime;
int prim[N + 10];
int pn = 0;
ll ans, n;
int w[10] = {1};
int pw[N];
void pre(int N) {
    rep(i, 1, 9) w[i] = w[i - 1] * 10;
    isPrime.set();
    for (int i = 2; i <= N; i++) {
        if (isPrime[i]) pw[pn] = w[int(log10(i) + 1)], prim[pn++] = i;
        for (int j = 0; j < pn && i * prim[j] <= N; ++j) {
            isPrime[i * prim[j]] = 0;
            if (i % prim[j] == 0) break;
        }
    }
}
void dfs(int pos, ll key, ll val) {
    ans = (ans + val) % mod;
    for (int i = pos; i < pn; ++i) { // merge with bigger prime num
        if (key * prim[i] > n) return;
        dfs(i, key * prim[i], (val * pw[i] % mod + prim[i]) % mod);
    }
}
int main() {
    sc(n);
    pre(n);
    for (int i = 0; i < pn && prim[i] <= n; ++i)
        // start dfs from each prime num
        dfs(i, prim[i], prim[i]);
    pr(ans);
    return 0;
}

这种做法时间复杂度本题70ms

牛牛想要成为hacker

本题很容易想到用斐波那契数列构建,但是fib很快就会超过1e9,数据项并不够。

所以:

  • 不可能找不到三角形,只能推迟,但无法阻止
  • 时间复杂度提示
>>> from math import log2
>>> log2(100000)
16.609640474436812

比赛的时候我想到了这里,但当时以为就是要让i循环走到16之后即可,所构建的数据也让i循环走到了21。但其实这是不够的:因为对于每个i循环,k并没有走次。

所以为了让次数尽可能多,必须降序构建。

最优的构造应当是降序的fib序列。

fib = [1, 1]
while fib[-1]+fib[-2] < 1e9:
    fib.append(fib[-1]+fib[-2])
fib.reverse()
n = int(input())
sz = len(fib)
for i in range(n):
    print(fib[i] if i < sz else 1, end=' ')

这也是一种比较有趣的写法

#include <bits/stdc++.h>
#define sc(x) scanf("%lld", &(x))
#define pr(x) printf("%lld ", (x))
#define rep(i, l, r) for (int i = (l); i <= (r); ++i)
using namespace std;
typedef long long ll;
const int N = 1e5 + 7;
int main() {
    int n;
    scanf("%d", &n);
    int x = 1 << 29;
    for(int i = 1;i <= n;i++){
        printf("%d ", x);
        if(x > 1) x >>= 1;
    }
    return 0;
}