杂题
2025-08-20

困难


B. beauty(拆贡献)

http://222.180.160.110:61235/contest/6513/problem/2

给定 \(n,V\),求出对于所有长度为 \(n\),值域为 \([1,V]\) 的序列 \(a_{1\sim n}\)\(\sum_{i=1}^n |a_i-a_{n-i+1}|\) 的和。

\(n,V\le 5000\)

  • 容易想到算贡献,会有一个 \(O(n^2V)\) 的做法。但是想 \(O(nV)\) 做是很抽象的,和 \(O(n^2V)\) 的思路已经很不一样了
  • 经典 trick,\(a_{i+n/2}-a_i=\sum\limits_{x=0}^{+\infty} [a_i\le x<a_{i+n/2}]\) 拆贡献 。故要算 \(a_{i}-a_{i+n/2}\),只需要对于每个 \(x\in[a_i,a_{i+n/2})\) 计算贡献。
  • 枚举 \(x\in [1,V)\),再枚举最大的 \(t\),满足 \(a_t\ge x\)。那么有 \(t\)\(a_i\le x\),同时有 \(n-t\)\(a_i>x\);满足 \(i\le n/2\)\(a_{i},a_{i+n/2}\) 对数应该是 \(\min(t,n-t)\)。故对于一个确定的序列,\(x\) 共有 \(\min(t,n-t)\) 的贡献。
  • 考虑计数满足 \(a_t\le x\)\(a\),这要求第 \(t\) 大的数 \(\le x\) 而第 \(t+1\) 大的数 \(>x\),也即在 \([1,x]\) 里找 \(t\) 个数再在 \((x,V]\) 里找 \(n-t\) 个数,注意还要再乘上这两种数拼起来的方案数。
#include <bits/stdc++.h>
const int mod = 998244353;
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
    std::freopen("beauty.in", "r", stdin);
    std::freopen("beauty.out", "w", stdout);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
    const auto stime = std::chrono::steady_clock::now();
#endif
    int T;
    std::vector<long long> fac(5001), inv(5001);
    std::vector<std::vector<long long> > pw(5001, std::vector<long long> (5001));
    fac[0] = inv[0] = 1ll;
    for (int i = 1; i <= 5000; ++i) {
        fac[i] = fac[i - 1] * i % mod;
        pw[i][0] = 1ll;
        for (int j = 1; j <= 5000; ++j)
            pw[i][j] = pw[i][j - 1] * i % mod;
    }
    auto qkp = [](long long x, int y) {
        auto res(1ll);
        for (; y; (x *= x) %= mod, y >>= 1)
            if (y & 1)
                (res *= x) %= mod;
        return res;
    };
    inv[5000] = qkp(fac[5000], mod - 2);
    for (int i = 4999; i; --i)
        inv[i] = inv[i + 1] * (i + 1) % mod;
    auto C = [&](int n, int m) {
        return fac[n] * inv[m] % mod * inv[n - m] % mod;
    };
    for (std::cin >> T; T--; ) {
        int n, V;
        std::cin >> n >> V;
        auto res(0ll);
        for (int x = 1; x < V; ++x)
            for (int t = 1; t < n; ++t) {
                int k = std::min(t, n - t);
                (res += k * pw[x][t] % mod * pw[V - x][n - t] % mod * C(n, t) % mod) %= mod;
            }
        std::cout << res * 2 % mod << '\n';
    }
#ifndef ONLINE_JUDGE
    std::cerr << std::fixed << std::setprecision(6) << std::chrono::duration<double> (std::chrono::steady_clock::now() - stime).count() << "s\n";
#endif
    return 0;
}

C. Drink Bar(容斥 + 偏序)

https://www.luogu.com.cn/problem/AT_snuke21_j

  • 三个属性都是排列,可以推理出只要两个三元组中,作出贡献的元素不完全相同,两个三元组就不同。讨论作出贡献的元素数量。
  • 只有一个元素作出贡献,答案为 \(n\)
  • 有两个元素作出贡献,任选的话答案为 \(C_n^2\),要减去一个元素严格优于另一个元素的情况,三维偏序即可。
  • 有三个元素作出贡献,是个有点复杂的容斥,不妨设三元组为 \((i,j,k)\),其中贡献次数最多的为 \(i\)

    1. \(i\) 贡献了 \(\ge 1\) 次,方案数为 \(C_n^3\)
    2. \(i\) 贡献了 \(\ge 2\) 次,枚举作出两次贡献的属性,以 \(a,b\) 为例,那么有 \(a_j,a_k<a_i\),以及 \(b_j,b_k<b_i\),二维偏序即可
    3. \(i\) 贡献了 \(\ge 3\) 次,依然是三维偏序,可以用『两个元素做出贡献』中 cdq 得到的值算出答案。记得乘 2,因为被多减了 2 次。
#include <bits/stdc++.h>
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
    const auto stime = std::chrono::steady_clock::now();
#endif
    int n;
    std::cin >> n;
    struct node { int a, b, c, res; };
    std::vector<node> a(n + 1);
    for (int i = 1; i <= n; ++i) {
        std::cin >> a[i].a >> a[i].b >> a[i].c;
        a[i].res = 0;
    }
    long long res = n;
    res += (long long)n * (n - 1) / 2;
    std::vector<int> bit(n + 1);
    auto lowbit = [](int x) {
        return x & -x;
    };
    auto add = [&](int x, int v) {
        for (; x <= n; x += lowbit(x))
            bit[x] += v;
        return;
    };
    auto ask = [&](int x) {
        int res = 0;
        for (; x; x -= lowbit(x))
            res += bit[x];
        return res;
    };
    std::function<void(int, int)> calc = [&](int l, int r) {
        if (l == r)
            return;
        int mid = (l + r) >> 1;
        calc(l, mid), calc(mid + 1, r);
        int j = l;
        for (int i = mid + 1; i <= r; ++i) {
            for (; j <= mid && a[j].b < a[i].b; ++j)
                add(a[j].c, 1);
            a[i].res += ask(a[i].c);
        }
        for (int i = l; i < j; ++i)
            add(a[i].c, -1);
        std::inplace_merge(a.begin() + l, a.begin() + mid + 1, a.begin() + r + 1, [&](node x, node y) { return x.b < y.b; });
        return;
    };
    std::sort(a.begin() + 1, a.end(), [&](node x, node y) { return x.a < y.a; });
    calc(1, n);
    for (int i = 1; i <= n; ++i)
        res -= a[i].res;
    res += (long long)n * (n - 1) * (n - 2) / 6;
    for (int k = 0; k < 3; ++k) {
        std::sort(a.begin() + 1, a.end(), [&](node x, node y) { return x.a < y.a; });
        std::fill(bit.begin() + 1, bit.end(), 0);
        for (int i = 1; i <= n; ++i) {
            int t = ask(a[i].b);
            res -= (long long)t * (t - 1) / 2;
            add(a[i].b, 1);
            std::tie(a[i].a, a[i].b, a[i].c) = std::make_tuple(a[i].b, a[i].c, a[i].a);
        }
    }
    for (int i = 1; i <= n; ++i)
        res += (long long)a[i].res * (a[i].res - 1);
    std::cout << res << '\n';
#ifndef ONLINE_JUDGE
    std::cerr << std::fixed << std::setprecision(6) << std::chrono::duration<double> (std::chrono::steady_clock::now() - stime).count() << "s\n";
#endif
    return 0;
}

C. 星白 by TTpandaS(笛卡尔树 + dsu on tree)

link


C. isn

https://hydro.ac/p/bzoj-P4361

  • 最后一个删去的一定是连接 > 的数,可以 DP 还剩一个数没删时可能的序列。

    \(f_{i,j,0/1}\) 表示最后一个元素为 \(i\),序列长为 \(j\),最后一个被删去的数(未)被确定的方案数。注意确定最后一个被删去的数要在转移过程中进行,而不是作为一个 DP 节点,很容易发现后者是错的。
  • 优化的思路就不一样了。因为要乘上 \((n-j)!\),所以 \(j\) 的这一维是省不掉的

    考虑不管最后一个被删掉的数,直接令 \(f_{i,j}\) 表示最后一个元素为 \(i\),序列长为 \(j\) 的方案数。有 \(f_{i,j}=\sum\limits_{a_k\le a_i}f_{k,j - 1}\),可以 DS 优化。但这样会产生不合法的情况。
  • 考察什么样的序列合法,发现删去的最后一个数一定是非法的,也就是包含之的序列都是非法的;反之易得被合法序列包含的序列都非法

    明白了这一点过后就会知道长度为 \(j\) 的合法序列系数都为 \((n-j)!\)

    故容斥,令 \(g_i\) 表示序列长为 \(i\) 的方案数,\(h_i\) 表示序列长为 \(i\) 的合法方案数。从异或角度考虑,易得 \(h_i=g_i-\sum\limits_{j=i+1}h_j\times (j-i)!\times C_j^i\)

#include <bits/stdc++.h>
const int mod = 1e9 + 7;
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
    std::freopen("isn.in", "r", stdin);
    std::freopen("isn.out", "w", stdout);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
    const auto stime = std::chrono::steady_clock::now();
#endif
    int n;
    std::cin >> n;
    std::vector<int> a(n + 1), l(1);
    std::vector<long long> fac(n + 1), inv(n + 1);
    fac[0] = inv[0] = 1ll;
    for (int i = 1; i <= n; ++i) {
        std::cin >> a[i], l.push_back(a[i]);
        fac[i] = fac[i - 1] * i % mod;
    }
    auto qkp = [](long long x, int y) {
        auto res(1ll);
        for (; y; (x *= x) %= mod, y >>= 1)
            if (y & 1)
                (res *= x) %= mod;
        return res;
    };
    inv[n] = qkp(fac[n], mod - 2);
    for (int i = n - 1; i; --i)
        inv[i] = inv[i + 1] * (i + 1) % mod;
    auto C = [&](int n, int m) {
        return fac[n] * inv[m] % mod * inv[n - m] % mod;
    };
    std::sort(l.begin(), l.end());
    l.erase(std::unique(l.begin(), l.end()), l.end());
    for (int i = 0; i <= n; ++i)
        a[i] = std::lower_bound(l.begin(), l.end(), a[i]) - l.begin() + 1;
    int m = (int)l.size();
    std::vector<std::vector<long long> > bit(n + 1, std::vector<long long> (m + 1));
    auto lowbit = [](int x) {
        return x & -x;
    };
    auto add = [&](int id, int x, long long v) {
        for (; x <= m; x += lowbit(x))
            (bit[id][x] += v) %= mod;
        return;
    };
    auto ask = [&](int id, int x) {
        auto res(0ll);
        for (; x; x -= lowbit(x))
            (res += bit[id][x]) %= mod;
        return res;
    };
    std::vector<long long> g(n + 1), h(n + 1);
    std::vector<std::vector<long long> > f(n + 1, std::vector<long long> (n + 1));
    add(0, a[0], 1ll), f[0][0] = 1ll;
    for (int i = 1; i <= n; ++i)
        for (int j = n; j; --j) {
            f[i][j] = ask(j - 1, a[i]);
            add(j, a[i], f[i][j]);
            (g[j] += f[i][j] * fac[n - j]) %= mod;
        }
    auto res(0ll);
    for (int i = n; i; --i) {
        h[i] = g[i];
        for (int j = i + 1; j <= n; ++j)
            (h[i] += mod - h[j] * fac[j - i] % mod * C(j, i) % mod) %= mod;
        (res += h[i]) %= mod;
    }
    std::cout << res << '\n';
#ifndef ONLINE_JUDGE
    std::cerr << std::fixed << std::setprecision(6) << std::chrono::duration<double> (std::chrono::steady_clock::now() - stime).count() << "s\n";
#endif
    return 0;
}

D. ThePowers

TopCoder - 12185,原题交不了故不放链接了

http://222.180.160.110:61235/contest/6522/problem/4

给定 \(A,B\),对于所有 \(X\le A,Y\le B\),求 \(X^Y\) 的可能取值数量。\(A,B\le10^9\)

  • 考虑什么时候算重。发现当且仅当 \(x^a=y^b\),此时记 \(a'=a\div\gcd(a,b),b'=b\div \gcd(a,b)\),那么 \(t=\sqrt[a']x=\sqrt[b']y\) 为整(写成质因数乘积是易证的),则 \(x=t^b,y=t^a\),是同一个数的不同次幂
  • 故把 \(A\) 内所有数分组,记 \(S_x\) 表示所有 \(x\) 的次幂,其中 \(x\) 不是其他数的次幂

    发现一个对于 \(>\sqrt A\) 的数 \(y\),只有可能属于 \(S_y\),或一个 \(x\le \sqrt A\)\(S_x\)。每组最多有 \(30\) 个,故扫一遍 \(\le\sqrt A\) 的数即可完成分组。
  • 这样就只用考虑同组内的计数。即对于 \(x\)\(p\le |S_x|,y\le B\)\(x^{py}\) 有多少种取值,也即 \(py\) 有多少种取值

    发现因为值域是连续的,对于一个 \(p\),只要 \(pB\) 范围内某个数是 \(p\) 的倍数就可以取到,枚举 \([(p-1)B+1,pB]\),对于每个 \(p\) 容斥,就需要计算 \(p\sim |S_x|\) 的每个子集,复杂度会爆炸。
  • 对于 \(x,y\in[p,|S_x|]\),如果 \(y\)\(x\) 的倍数,就可以 skip,只在剩下的元素里枚举子集,可以代码验证一下 \(30\) 以内最多剩下 \(15\) 个数,可以接受,注意子集信息类似高维前缀和地 \(O(1)\) 求就行了

#include <bits/stdc++.h>
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
    std::freopen("power.in", "r", stdin);
    std::freopen("power.out", "w", stdout);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
    const auto stime = std::chrono::steady_clock::now();
#endif
    long long A, B, mx = 1ll;
    std::cin >> A >> B;
    if (A == 1) {
        std::cout << 1 << '\n';
        return 0;
    }
    int cnt = 1;
    long long res = 1ll;
    for (; (mx + 1) * (mx + 1) <= A; ++mx);
    std::vector<int> tag(mx + 1);
    for (int i = 2; i <= mx; ++i)
        if (!tag[i]) {
            int siz = 1;
            for (long long j = i; j <= A; j *= i, ++siz)
                if (j <= mx)
                    tag[j] = 1;
            cnt += --siz;
            for (int j = 1; j <= siz; ++j) {
                std::vector<int> p({ j });
                for (int k = j + 1; k <= siz; ++k) {
                    bool flag = 1;
                    for (auto l : p)
                        if (k % l == 0) {
                            flag = 0;
                            break;
                        }
                    if (flag)
                        p.push_back(k);
                }
                int m = (int)p.size(), s = 1 << m;
                std::vector<long long> mul(s);
                mul[0] = 1ll;
                auto lcm = [&](long long x, long long y) {
                    return x / std::__gcd(x, y) * y;
                };
                for (int k = 1; k < s; ++k) {
                    mul[k] = lcm(p[std::__lg(k ^ ((k - 1) & k))], mul[(k - 1) & k]);
                    if (__builtin_popcount(k) & 1)
                        res += j * B / mul[k] - (j - 1) * B / mul[k];
                    else
                        res -= j * B / mul[k] - (j - 1) * B / mul[k];
                }
            }
        }
    res += (A - cnt) * B;
    std::cout << res << '\n';
#ifndef ONLINE_JUDGE
    std::cerr << std::fixed << std::setprecision(6) << std::chrono::duration<double> (std::chrono::steady_clock::now() - stime).count() << "s\n";
#endif
    return 0;
}

搜索做法本质上是一样的,就不赘述了


言论