NOIP模拟测试A3 赛后总结
阅读原文时间:2023年08月22日阅读:1

可以发现题目要求我们求的实际上是若干个连续整数 \(c_i\) ,使得 \(\displaystyle \prod c_i = n\),通过打表可以发现这些连续整数的长度 \(d\) 很小,毕竟 \(20~! > 10^{20}\) ,也就是说我们的 \(d \le 20\),所以我们可以去枚举 \(d\)。可以看出指定 \(d\) 之后,这一串数的值一定在 \(\displaystyle \sqrt[d]{n}\) 附近,直接暴力判断即可。

Code

//A - calc
#include<bits/stdc++.h>

typedef long long valueType;

constexpr valueType maxK = 21;

valueType check(valueType N, valueType k);

void solve(valueType N);

int main() {
    int T;

    std::cin >> T;

    for(int i = 1; i <= T; ++i) {
        valueType N;

        std::cin >> N;

        solve(N);
    }

    std::cout << std::flush;

    return 0;
}

void solve(valueType N) {
    std::vector<std::pair<valueType, valueType>> ans;

    if(N == 1) {
        std::cout << -1 << '\n';
        return;
    }

    ans.emplace_back(N, N - 1);

    for(int k = 2; k <= maxK; ++k) {
        valueType const result = check(N, k);

        if(result != -1)
            ans.emplace_back(result + k - 1, result - 1);
    }

    std::sort(ans.begin(), ans.end());

    std::cout << ans.size() << '\n';

    for(auto const &iter : ans)
        std::cout << iter.first << ' ' << iter.second << '\n';
}

valueType check(valueType N, valueType k) {
    valueType const a = std::floor(std::pow<long double>((long double)N, (long double)1 / k));

    for(valueType start = std::max(a - k - 1, (valueType)2); start <= a; ++start) {
        valueType result = 1, i = start;

        for(i = start; i < start + k && result < N; ++i)
            result *= i;

        if(result == N && i == start + k)
            return start;
    }

    return -1;
}

构造题,首先特判掉 \(k = 1\) 和 \(k = n\) 的情况,然后如果 \(\displaystyle k \nmid \sum_{i = 1}^ni\) 的话,也判无解。

设 \(\displaystyle m = \frac{n}{k}\),如果 \(m\) 为偶数,那么我们可以让从 \(1\) 到 \(n\) 这 \(n\) 个数中 \(i\) 和 \(n - i\) 两两配对放到一个子集中,这样可以保证每个子集的和都是一定的。

我们接下来考虑 \(m\) 为奇数的情况,我们可以把 \(m\) 分解为 \(m = 2r + 3\) 对于每个子集我们特殊处理 \(3\) 组数,剩余的 \(2r\) 组按照上面说的处理。

我们希望 这 \(3\) 组数的和相等,我们将其标号为 \(1,2,3,\cdots,3n\),我们先将标号为 \(1,2,\cdots, n\) 的数分别放到一个集合里,这时候这些数构成了一个等差数列,其公差为 \(1\),那么接下来我们就要让剩下 \(2\) 组数的和构成一个公差为 \(-1\) 的等差数列。我们可以让一组数的大部分公差取到 \(1\) ,另一部分则取到 \(-2\),如果公差为 \(1\) 那么我们就让 \(i + 1\) 排在 \(i\) 的后面,如果公差为 \(-2\) 那么我们就让 \(i\) 排在 \(i + 2\) 的后面,不难看出 \(i\) 和 \(i + 2\) 的奇偶性相同,所以我们把 \(2n + 1, 2n + 2, \cdots, 3n\) 这 \(n\) 个数分类为奇数和偶数,相同的我们把 \(n + 1, n + 2, \cdots, 2n\) 也分为两类,每类的数量与下一组相同,因为 \(n\) 为奇数,所以可以看出这个数列的中心位置肯定是一个奇数 \(3n\) ,再考虑到公差为 \(-2\),那么前一位就是 \(2n + 2\) ,所以这一个位置他们的差为 \(n - 2\) ,我们要通过在另一组数中选取合适的数让他们的差为 \(-1\) ,可以看到 \((2n + 2) + 2n\) 与 \(3n + (n + 1)\) 的差也为1,所以我们就可以确定另外一组分成的两类的起始点和终点,这样我们就构造出了一种可行的排序方法。

Code

//B
#include<bits/stdc++.h>

typedef long long valueType;

void solve(valueType N, valueType K);

int main() {
    std::ios::sync_with_stdio(false);

    int T;

    std::cin >> T;

    for(int i = 1; i <= T; ++i) {
        valueType N, K;

        std::cin >> N >> K;

        solve(N, K);
    }

    std::cout << std::flush;

    return 0;
}

void solve(valueType N, valueType K) {
    if(N == 1 && K == 1) {
        std::cout << "Yes\n1\n";
        return;
    }

    valueType const sum = N * (N + 1) / 2;

    if(sum % K != 0 || N == K) {
        std::cout << "No\n";
        return;
    }

    valueType const count = N / K;

    std::vector<std::queue<valueType>> ans(K);

    if(count & 1) {
        if((K & 1) == 0) {
            std::cout << "No\n";
            return;
        }

        valueType i = 1;

        valueType const mid = (K + 1) / 2;

        for(int j = 0; j < K; ++j)
            ans[j].push(i++);

        for(int j = mid; j < K; ++j)
            ans[j].push(i++);

        for(int j = 0; j < mid; ++j)
            ans[j].push(i++);

        i += K - 1;

        for(int j = 0, p = i; j < mid; ++j, p -= 2)
            ans[j].push(p);

        for(int j = mid, p = i - 1; j < K; ++j, p -= 2)
            ans[j].push(p);

        ++i;

        for(int c = 0; c < count - 3; ++c) {
            if(c & 1) {
                for(int j = 0; j < K; ++j)
                    ans[j].push(i++);
            } else {
                for(int j = K - 1; j >= 0; --j)
                    ans[j].push(i++);
            }
        }
    } else {
        valueType i = 1;

        for(int c = 0; c < count; ++c) {
            if(c & 1) {
                for(int j = 0; j < K; ++j)
                    ans[j].push(i++);
            } else {
                for(int j = K - 1; j >= 0; --j)
                    ans[j].push(i++);
            }
        }
    }

    std::cout << "Yes\n";

    for(auto &iter : ans) {
        while(!iter.empty()) {
            std::cout << iter.front() << ' ';

            iter.pop();
        }

        std::cout << '\n';
    }
}

这道题我们可以在 \(x\) 轴上建立线段树,把每个询问都对应到线段树的一个叶子节点上,我们发现一个询问可以得出答案当且仅当这个叶子节点上被累加的值超过询问的纵坐标。所以我们可以令叶子节点的权值为这一个点上待处理的询问中 \(y\) 值的最小值。

于是我们就可以类比出非叶子节点的权值:当前区间内的待处理的询问中 \(y\) 值的最小值。对于每次更改如果当前累加的值大于线段树的权值,我们就向下递归到被处理的询问的叶子节点去处理询问。每个询问最多引发一次向下递归,所以对处理所有操作的复杂度为 \(\displaystyle \mathcal{O}(QlogN)\),对询问进行排序的复杂度为 \(\displaystyle \mathcal{O}(QlogQ)\),建立线段树的复杂度为 \(\displaystyle \mathcal{O}(NlogN)\),总复杂度为 \(\displaystyle \mathcal{O}(QlogQ + QlogN + NlogN)\),在 \(Q, N\) 同阶的情况下,复杂度可看作 \(\displaystyle \mathcal{O}(NlogN)\)。

考虑到时间线对询问结果的影响,我们可以在进行更改操作的同时按照时间序列将之间的询问结果直接输出,这样就可以避免了非法时间的更改对询问结果的影响。

考场出现 MLE 是因为在存储每个节点对应的查询的时候使用了 std::queue,在存储少量数据的情况下(例如只有一个),std::vector 的空间要明显优于 std::queue,具体原因可见下文引用自 cppreference.com 的内容。

deque 的存储按需自动扩展及收缩。扩张 deque 比扩张 std::vector 更优,因为它不涉及到复制既存元素到新内存位置。另一方面,deque 典型地拥有较大的最小内存开销;只保有一个元素的 deque 必须分配它的整个内部数组(例如 64 位 libstdc++ 上是对象尺寸的 8 倍;64 位 libc++ 上是对象尺寸的 16 倍和 4096 字节中的较大者)。

Code

//C
#include<bits/stdc++.h>

typedef long long valueType;
typedef std::vector<valueType> ValueVector;

constexpr valueType MAX = LLONG_MAX >> 1;

ValueVector ans;

struct Query {
    int x;
    valueType y;
    int id;

    void solve(valueType x) {
        ans[id] = x;
    }

    Query(valueType _x_, valueType _y_, int i) : x(_x_), y(_y_), id(i) {};

    friend bool operator<(const Query &a, const Query &b) {
        if(a.y != b.y)
            return a.y > b.y;

        return a.id > b.id;
    }
};

typedef std::vector<Query> QueryVector;
typedef QueryVector::iterator QueryIter;
typedef std::vector<QueryIter> QueryQueue;

struct Operation{
    enum TYPE {
        MODIFY = 1,
        QUERY = 2
    };
    TYPE type;

    int l, r, x;
    valueType h;
};

typedef std::vector<Operation> OperateVector;

std::vector<QueryQueue> TreeQuery;
OperateVector operate;
QueryVector query;

class Tree{
private:
    struct NODE{
        valueType sum;
        valueType data;
        valueType lazy;

        NODE() : sum(0), data(MAX), lazy(0) {};
    };

    int size;

    std::vector<NODE> node;

    void pushUp(int id) {
        node[id].data = std::min(node[id << 1].data, node[id << 1|1].data);
    }

    void pushDown(int id) {
        if(node[id].lazy != 0) {
            node[id << 1].lazy += node[id].lazy;
            node[id << 1].data -= node[id].lazy;
            node[id << 1].sum += node[id].lazy;

            node[id << 1|1].lazy += node[id].lazy;
            node[id << 1|1].data -= node[id].lazy;
            node[id << 1|1].sum += node[id].lazy;

            node[id].lazy = 0;
        }
    }
public:
    Tree(int N) : size(N), node((size + 5) << 2) {
        build(1, 1, N);
    }

    void update(int l, int r, valueType key, int value) {
        update(1, l, r, 1, size, key, value);
    }

private:
    void build(int id, int l, int r) {
        if(l == r) {
            node[id].lazy = 0;
            node[id].data = TreeQuery[r].empty() ? MAX : TreeQuery[r].back()->y;

            return;
        }

        valueType const mid = (l + r) >> 1;

        build(id << 1, l, mid);
        build(id << 1|1, mid + 1, r);

        pushUp(id);
    }

    void update(int id, int queryL, int queryR, int nodeL, int nodeR, valueType key, int value) {
        if(nodeL == nodeR) {
            node[id].sum += key;

            if(node[id].data <= key) {
                TreeQuery[nodeR].back()->solve(value);
                TreeQuery[nodeR].pop_back();

                while(!TreeQuery[nodeR].empty() && TreeQuery[nodeR].back()->y <= node[id].sum) {
                    TreeQuery[nodeR].back()->solve(value);
                    TreeQuery[nodeR].pop_back();
                }

                node[id].data = TreeQuery[nodeR].empty() ? MAX : TreeQuery[nodeR].back()->y - node[id].sum;
            } else {
                node[id].data = node[id].data == MAX ? MAX : node[id].data - key;
            }

            return;
        }

        if(queryL <= nodeL && nodeR <= queryR) {
            node[id].sum += key;

            if(node[id].data <= key) {
                pushDown(id);

                valueType const mid = (nodeL + nodeR) >> 1;

                update(id << 1, queryL, queryR, nodeL, mid, key, value);
                update(id << 1|1, queryL, queryR, mid + 1, nodeR, key, value);

                pushUp(id);
            } else {
                node[id].data = node[id].data == MAX ? MAX : node[id].data - key;
                node[id].lazy = node[id].data == MAX ? 0 : node[id].lazy + key;
            }

            return;
        }

        pushDown(id);

        valueType const mid = (nodeL + nodeR) >> 1;

        if(queryL <= mid)
            update(id << 1, queryL, queryR, nodeL, mid, key, value);

        if(queryR > mid)
            update(id << 1|1, queryL, queryR, mid + 1, nodeR, key, value);

        pushUp(id);
    }
};

int main() {
    int N, Q;

    std::cin >> N >> Q;

    operate.resize(Q + 1);
    query.reserve(Q + 1);
    TreeQuery.resize(N + 1);
    ans.resize(Q + 1, 0);

    for(int i = 1; i <= Q; ++i) {
        int type;

        std::cin >> type;

        if(type == 1) {
            int l, r;
            valueType h;

            std::cin >> l >> r >> h;

            operate[i].type = Operation::MODIFY;
            operate[i].l = l;
            operate[i].r = r;
            operate[i].h = h;
            operate[i].x = i;
        } else if(type == 2) {
            int x;
            valueType y;

            std::cin >> x >> y;

            query.emplace_back(x, y, i);

            operate[i].type = Operation::QUERY;
            operate[i].x = i;
        }
    }

    query.shrink_to_fit();

    std::sort(query.begin(), query.end());

    for(auto iter = query.begin(); iter != query.end(); ++iter)
        TreeQuery[iter->x].push_back(iter);

    Tree tree(N);

    for(int i = 1; i <= Q; ++i) {
        if(operate[i].type == Operation::MODIFY) {
            tree.update(operate[i].l, operate[i].r, operate[i].h, operate[i].x);
        } else {
            std::cout << ans[operate[i].x] << '\n';
        }
    }

    std::cout << std::flush;

    return 0;
}

我们可以先算出不考虑管道损坏情况下每个节点的期望流量值,接下来我们考虑管道损坏对节点流量的影响。

我们能观察到,若 \(x\) 点的排水管道堵塞,从 \(x\) 点经过的污水吨数必然不变。

因此,我们可以借此考虑 \((x, y)\) 堵塞的影响:我们可以将管道堵塞视为 \(y\) 的经过污水吨数凭空减少一个定值,而 \(x\) 其他出点的污水吨数凭空增加一个定值。

我们将这一定值计算出来,作为节点流量的初始值,进行第二次计算,这样我们就可以统计出考虑管道损坏情况下的节点流量值。

Code

//D
#include<bits/stdc++.h>

typedef long long valueType;
typedef std::vector<valueType> ValueVector;

constexpr valueType MOD = 998244353;

class Inverse{
private:
    valueType size;

    ValueVector data;

public:
    Inverse(valueType N) : size(N), data(N, 0) {
        data[1] = 1;

        for(valueType i = 2; i < N; ++i)
            data[i] = (long long)(MOD - MOD / i) * data[MOD % i] % MOD;
    }

    valueType operator()(valueType i) const {
        i %= MOD;

        if(i < size)
            return data[i];

        return (MOD - MOD / i) * operator()(MOD % i) % MOD;
    }
};

typedef std::pair<int, valueType> EdgePair;
typedef std::list<EdgePair> EdgeList;
typedef std::vector<EdgeList> EdgeSet;

int main() {
    valueType N, M, R, K;

    std::cin >> N >> M >> R >> K;

    EdgeSet edge(N + 3);
    ValueVector degree(N + 3, 0), sumA(N + 3, 0), in(N + 3, 0);
    valueType S = 0;

    typedef std::function<void(int, int, valueType)> addEdgeFunction;

    addEdgeFunction addEdge = [&edge, &degree, &in, &sumA, &S] (int u, int v, valueType weight) mutable {
        edge[u].emplace_back(v, weight); 

        ++degree[u];
        ++in[v];

        S = (S + weight) % MOD;

        sumA[u] = (sumA[u] + weight) % MOD;
    };

    for(int i = 0; i < K; ++i) {
        int x, y;
        valueType a;

        std::cin >> x >> y >> a;

        addEdge(x, y, a);
    }

    //前有绿豆蛙的归宿
    //今有下水道的排出

    Inverse Inv(N + 10);

    valueType const InvS = Inv(S);

    std::queue<int> que;

    ValueVector dp(N + 3, 0);

    for(int i = 1; i <= M; ++i) {
        dp[i] = 1;
        que.push(i);
    }

    ValueVector InCopy = in;

    while(!que.empty()) {
        int const x = que.front();
        que.pop();

        for(auto const &iter : edge[x]) {
            int const to = iter.first;

            dp[to] = (dp[to] + dp[x] * Inv(degree[x]) % MOD) % MOD;

            if(--InCopy[to] == 0)
                que.push(to);
        }
    }

    ValueVector F(N + 3, 0);

    for(int i = 1; i <= N; ++i) {
        for(auto const &iter : edge[i]) {
            int const to = iter.first;
            int const weight = iter.second;

            F[i] = ((__int128)F[i] + (__int128)dp[i] * Inv(degree[i] - 1) % MOD * weight % MOD * InvS % MOD) % MOD;
            F[to] = (((__int128)F[to] - ((__int128)dp[i] * Inv(degree[i] - 1) % MOD * weight % MOD * InvS % MOD)) % MOD + MOD) % MOD;
        }
    }

    InCopy = in;

    for(int i = 1; i <= M; ++i) {
        F[i] = (F[i] + 1) % MOD;
        que.push(i);
    }

    while(!que.empty()) {
        int const x = que.front();
        que.pop();

        for(auto const &iter : edge[x]) {
            int const to = iter.first;

            F[to] = (F[to] + F[x] * Inv(degree[x]) % MOD) % MOD;

            if(--InCopy[to] == 0)
                que.push(to);
        }
    }

    for(int i = N - R + 1; i <= N; ++i)
        std::cout << F[i] << ' ';

    std::cout << std::endl << std::flush;

    return 0;
}

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器