# 官方题解 | 第三届飞翔杯提高组
2025-06-14 17:38:50
发布于:浙江
T1
题解
记 。
直接模拟题意的时间复杂度是 。对求 的方法稍加优化即可做到 。
对于特殊性质 A,立刻注意到  当且仅当 
,故答案全 。
下面先给出正解。
首先,注意到  不变,等价于 
或 。充分性与必要性分别显然。
于是, 是稳定化子,当且仅当不存在一个子段 
,使得 
且 
;换句话说,即 
,都成立 。
进而想到枚举 
。记 ,。由于上一段分析中 
的任意性,可以发现 
是稳定化子,当且仅当 。
对应到最终的计数上,就是位置  单点加 , 中所有  的位置  加 
。至此本题应不再有任何困难,一个树状数组即可解决问题。
时间复杂度 ,空间复杂度 。
部分与正解思路接近或能够进行一些有效转化的选手,应当可以立刻将时间复杂度优化到 或 ,获得一些子任务的分数。
对于排列随机的情形,应当只有  对本质不同的 。如果选手的时间复杂度与 
有关,这一子任务可能有所帮助。
对于特殊性质 C, D,可以发现 的区间都是平凡的,只需考虑 的情形,可能部分选手会推出较弱的转化。
示例代码
# include <bits/stdc++.h>
namespace stabilizer {
    using namespace std;
    class fenwick {
        vector<int> t;
        void add(size_t x, int const d) { while (x < t.size()) t[x] += d, x += x & -x; }
    public:
        fenwick(size_t const n): t(n + 1) {
        }
        void add(size_t const l, size_t const r, int const d) { add(l + 1, +d), add(r + 2, -d); }
        int test(size_t x) const {
            ++x;
            int y(0);
            while (x) y += t[x], x &= x - 1;
            return y;
        }
    };
}
int main() {
    using namespace std;
    using stabilizer::fenwick;
    int n;
    cin >> n;
    vector<int> p(n);
    vector<int> q(n);
    for (int &p: p) cin >> p, --p;
    for (int i(0); i != n; ++i) q[p[i]] = i;
    stabilizer::fenwick bit(n);
    vector<int> c(n);
    for (int i(0), l(n), r(0); i != n; ++i) {
        c[q[i]] += bit.test(q[i]);
        l = min(l, q[i]), r = max(r, q[i]);
        if (l != q[i] && q[i] != r)
            c[q[i]] += r - l - i, bit.add(l, r, +1);
    }
    for (int i(0); i != n; ++i)
        i == n - 1 ? cout << c[i] << endl : cout << c[i] << ' ';
}
T2
题解
时,显然:若 则存在唯一的珍贵片段;否则不存在珍贵片段。
 时,分析顺序对可知:只有 
有可能成为珍贵片段,而这些片段的确珍贵,构造是非常容易的。
时,所有片段皆珍贵,构造性的思路见下。记 
。注意到,对于  中的位置,必须令其单调上升;对于其余位置,令其单调下降是不劣的。可以考虑:对于 
中的位置,能否令其要么无顺序前驱,要么无顺序后继?这个问题的答案是肯定的,只需令 
在值域上连续即可。于是构造呼之欲出:在  前令  中位置的取值尽可能大;在 
后令其尽可能小即可。至此,我们对任意的 ,构造出了一个满足条件的 。于是所有片段皆珍贵。
故答案为 。
直接计算的时间复杂度可以轻松做到 ,优化为 
的技巧无非应用 。
示例代码
# include <bits/stdc++.h>
int main() {
  using namespace std;
  constexpr int64_t P(998244353);
  int64_t t;
  cin >> t;
  for (int64_t _(1); _ <= t; ++_) {
    int64_t n, m;
    cin >> n >> m;
    static vector<int64_t> inv(1);
    inv.reserve(m + 1);
    while (inv.size() <= size_t(m)) {
      int64_t const x(inv.size());
      inv.push_back(x == 1 ? 1 : inv[P % x] * (P - P / x) % P);
    }
    auto const binom = [] (int64_t const n, int64_t const m) {
      int64_t res(1);
      for (int64_t i(1); i <= m; ++i) res = res * (n - i + 1) % P;
      for (int64_t i(1); i <= m; ++i) res = res * inv[i] % P;
      return res;
    };
    vector<int64_t> ans(m + 1);
    for (int64_t k(1); k <= m; ++k)
      switch (k) {
        case 1 : ans[k] = n == 1; break;
        case 2 : ans[k] =  (n - 1)%P; break;
        case 3 : ans[k] = binom(n, k); break;
        default: ans[k] = (n - k + 1) * inv[k] % P * ans[k - 1] % P; break;
      }
    
    cout << accumulate(ans.begin(), ans.end(), 0ll, bit_xor<int64_t>()) << endl;
  }
}
T3
题解
首先进一步压缩使得 。
先考察回文子串的形式。
对于一个子串 ,考察其跨过的压缩信息数 。
首先注意到 的子串不可能回文。
的子串自然回文。
对于  的子串,可以发现除头尾外的  个压缩信息必定构成压缩信息意义上的长度为  的回文串。所以,直接对压缩信息运行
Manacher 算法或哈希后二分,求出以每个位置为中心的最长回文半径,同时对 
做前缀和,即可简单勾勒出以每条信息为中心的  的回文子串之样貌。
于是,回文串被自然地分成了两类。
对于第一类,设该压缩信息为 ,则对答案的贡献为
而 是经典的,于是上式可 计算。
对于第二类,同理上类,贡献无非以下形式:
亦可 计算。
若使用 Manacher 算法,总时空复杂度皆为 。
示例代码
# include <bits/stdc++.h>
int main() {
    using namespace std;
    long long sum_len = 0;
    int64_t P(998244353);
      auto inv = [&](__int128 x) {
        x = (x % P + P) % P, assert(0 < x && x < P);
        __int128 y(1);
        while (x != 1) y = y * -(P / x) % P, x = P % x;
        return (y + P) % P;
    };
    __int128 inv2(inv(2));
    __int128 inv6(inv(6));
    __int128 inv24(inv(24));
      auto e2
            = [&](__int128 const n) { return n * (n + 1) / 2; };
      auto e4
            = [&](__int128 const n) { return n * (n + 1) % P * (n + 2) % P * (n + 3) % P * inv24 % P; };
      auto p2
            = [&](__int128 const n) { return n * (n + 1) % P * (2 * n + 1) % P * inv6 % P; };
     auto s2
            = [&](int64_t const n) { return (e2(n) * (n + 1) - p2(n)) % P; };
    int64_t n;
    cin >> n;
    vector<pair<__int128, char> > s;
    for (__int128 i(0); i != n; ++i) {
        int64_t a;
        char b;
      
        cin >> a >> b;
          sum_len += a;
        if (s.empty() || b != s.back().second) s.emplace_back(0, b);
        s.back().first += a;
    }
    vector<__int128> r(n = s.size());
    for (__int128 i(0), j(0); i != n; ++i) {
        if (i <= j + r[j]) r[i] = min(r[2 * j - i], j + r[j] - i);
        while (0 < i - r[i] && i + r[i] + 1 < n)
            if (s[i - r[i] - 1] == s[i + r[i] + 1]) ++r[i];
            else break;
        if (i + r[i] > j + r[j]) ++j;
    }
    vector<__int128> t(n + 1);
    for (__int128 i(0); i != n; ++i)
        t[i + 1] = t[i] + s[i].first;
    int64_t ans(0);
    for (__int128 i(0); i != n; ++i) {
        __int128   o(s[i].first);
        __int128   u(t[i]);
        __int128   v(t[n] - t[i + 1]);
        o%=P,u%=P , v%=P;
        ans = (ans + (u * v) % P * e2(o)) % P;
        ans = (ans + (u + v) % P * s2(o)) % P;
        ans = (ans + e4(o)) % P;
    };
    for (__int128 i(0); i != n; ++i) {
        __int128 len(t[i] - t[i - r[i]]);
        if (0 < i - r[i] && i + r[i] + 1 < n)
            if (s[i - r[i] - 1].second == s[i + r[i] + 1].second)
                len += min(s[i - r[i] - 1].first, s[i + r[i] + 1].first);
        __int128   u(t[i]), v(sum_len - t[i + 1]);;
        len %= P;
        u%=P , v%=P;
        ans = (ans + (u * v) % P * len) % P;
        ans = (ans - (u + v) % P * e2(len - 1)) % P;
        ans = (ans + p2(len - 1)) % P;
    }
    cout << (ans + P) % P << endl;
}
T4
题解
记 。记全体质数构成的集合为 。
正解之思路是直接的:
其中, 可以线性筛出; 及最后一个  均为 Dirichlet
前缀和或后缀和的形式,可以  时间  空间求出。
于是,在  时间  空间的预处理后,可以单次  地回答询问。总时间复杂度 
,总空间复杂度 。
下就部分子任务给出简要思路。
对于测试点 ,直接或在对 中 计数后,模拟题意即可。
对于特殊性质 A, C,注意到以下事实后,对每个质数 记录 与 ,进而求出答案并无任何困难。
对于特殊性质 B1,其思路与正解几乎全同,无非在推导过程中将一些 改为 而已。
示例代码
# include <bits/stdc++.h>
int main() {
    using namespace std;
    int n;
    cin >> n;
    vector<int> a(n);
    for (int &a: a) cin >> a;
    int m;
    cin >> m;
    vector<int> b(m);
    for (int &b: b) cin >> b;
    int const A(*max_element(a.begin(), a.end()));
    int const B(*max_element(b.begin(), b.end()));
    int const C(max(A, B));
    vector<int> isprime(C + 1, true);
    vector<int> prime, mu(C + 1);
    isprime[1] = isprime[0] = false, mu[1] = 1;
    for (int i(2); i <= C; ++i) {
        if (isprime[i]) {
            prime.push_back(i);
            mu[i] = -1;
        }
        for (int const j: prime) {
            if (i * j > C) break;
            isprime[i * j] = false;
            if (i % j) mu[i * j] = -mu[i];
            else break;
        }
    }
    vector<int64_t> f(C + 1);
    vector<int64_t> g(C + 1);
    vector<int64_t> h(C + 1);
    for (int i(1); i <= C; ++i) f[i] = i * mu[i];
    for (int const p: prime)
        for (int i(1); p * i <= C; ++i) f[p * i] += f[i];
    for (int const a: a) g[a] += a;
    for (int const p: prime)
        for (int i(C / p); i >= 1; --i) g[i] += g[p * i];
    for (int i(1); i <= C; ++i) h[i] = g[i] / i * f[i];
    for (int const p: prime)
        for (int i(1); p * i <= C; ++i) h[p * i] += h[i];
    for (int const b: b) cout << h[b] << endl;
}
全部评论 10
老师的码风也是风韵犹存(vector
2025-06-14 来自 浙江
1是极为少见的动态数组派
2025-06-14 来自 浙江
09494
2025-06-15 来自 广东
0
666
2025-06-14 来自 广东
1现在的 T3 std 还是挂的,我正确的代码只有 44 分。
2025-06-14 来自 北京
1zc
2025-06-14 来自 浙江
0啊,你怎么确定自己的事正确代码(?)(xxs,喷轻点
2025-06-14 来自 浙江
0事->是
2025-06-14 来自 浙江
0
赛时 T1 只拿了 60pts,结果现在发现加个懒标记就能 A 的



2025-07-27 来自 湖南
0额滴算法竞赛啊啊啊啊啊啊啊
2025-07-27 来自 湖南
0
2025-06-16 来自 北京
0T1 似乎有 O(n) 做法
2025-06-16 来自 河南
0好吧,已经修改了数据,也就是说,2h 能 AK 的比赛硬是拖了我 1h 来给 std 查错。
2025-06-16 来自 北京
0%%%
2025-06-15 来自 北京
0啊……
2025-06-14 来自 浙江
0沙发
2025-06-14 来自 广东
0





































有帮助,赞一个