2024 CCPC 郑州邀请赛暨河南省赛 题解

A - Once In My Life (构造)

分析

根据题目要求需要两个d,并且要保证123456789都存在,那么就可以尝试先构造出一个数保证存在123456789。
尝试以下构造方式:设n的长度为len,则可以构造出\(k*n=({\frac{123456789*10^{len + 1}}n + 1}) * n\), 可以发现\(k*n\)的前缀一定是\(1234567890X\),此时只要对\(k\)每次+1,至多一百次一定能出现想要的\(d\)

代码实现

#include <bits/stdc++.h>
int main() {
    std::cin.tie(nullptr)->sync_with_stdio(false);
    #define int long long
    constexpr int N = 123456789LL;
    int tt;
    for (std::cin >> tt; tt--;) {
        int n, d;
        std::cin >> n >> d;
        int k = 123456789LL * std::pow<int>(10, 2 + (int)std::log10<int>(n)) / n + 1;
        auto check = [&](int w) {
            std::vector<int> c(10);
            int num = w * n;
            while (num) c[num % 10] += 1, num /= 10;
            for (int i = 1; i <= 9; ++i) if (c[i] < 1) {
                return false;
            }
            return c[d] >= 2;
        };
        while (!check(k)) k += 1;
        std::cout << k << '\n';
    }
}

B - 扫雷 1

分析

队友写的,并没有看题,就贴一下队友代码和思路:
贪心。要使得买的探测仪最多,就要尽可能的买价格较便宜的,相同便宜的要买后面出现的,因为这样手中的钱会更多。所以我们只需要把价格作为第一关键词升序排序,出现序号作为第二关键词降序排序,然后一个一个尽可能的多买,直到已经到最后一个时退出就行

代码实现

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    
    int n;
    cin >> n;
    vector<tuple<ll , ll>> tl;
    for (int i=1; i<=n; ++ i) {
        int x;
        cin >> x;
        tl.emplace_back(x , -i);
    }
    sort(tl.begin() , tl.end());
    ll ans = 0 , cur = 0 , pre = 0;
    for (auto [a , b] : tl) {
        b = -b;
        if (b >= a && b > pre) {
            ans += (b - cur) / a;
            cur += (b - cur) / a * a;
            pre = b;
        }
        if (b == n) break;
    }
    std::cout << ans << "\n";
}

C - 中二病也要打比赛 (树状数组+dp)

分析

依旧队友神力

代码实现

#include <bits/stdc++.h>
using namespace std;
template <typename T>
struct Fenwick {
    int n;
    vector<T> d;
    Fenwick() : n(0) {}
    Fenwick(int n) : n(n), d(n) {}
    void modify(int x, T v) {
        assert(0 <= x && x < n);
        x += 1;
        while (x <= n) {
            d[x - 1] = max(d[x - 1], v);
            x += x & -x;
        }
    }
    T get(int x) {
        assert(x <= n);
        T v = 0;
        while (x > 0) {
            v = max(v, d[x - 1]);
            x -= x & -x;
        }
        return v;
    }
    T get(int l, int r) {
        assert(l <= r);
        return get(r) - get(l);
    }
    int min_right(T k) {
        int r = 0;
        for (int e = __lg(n); ~e; --e) {
            int u = (1 << e);
            if (r + u <= n && d[r + u - 1] < k) {
                k -= d[(r += u) - 1];
            }
        }
        return r;
    }
};
int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
  #define int long long
  int n;
  cin >> n;
  vector<int> a(n);
  for (int i = 0; i < n; ++i) {
    cin >> a[i];
    --a[i];
  }
  vector<int> l(n, n);
  vector<int> r(n, 0);
  for (int i = 0; i < n; ++i) {
    l[a[i]] = min(l[a[i]], i);
    r[a[i]] = max(r[a[i]], i);
  }
  for (int i = n - 1; i >= 0; ) {
    assert(i == r[a[i]]);
    int j = l[a[i]], k = i;
    while (k >= j) {
      r[a[k]] = i;
      j = min(j, l[a[k]]);
      --k;
    }
    i = k;
  }
  Fenwick<int> fen(n);
  vector<int> dp(n);
  for (int i = 0; i < n; ++i) {
    assert(i == l[a[i]]);
    for (int j = i; j <= r[a[i]]; ++j) {
      if (j == l[a[j]]) {
        dp[l[a[j]]] = fen.get(a[j]) + 1;
      }
    }
    for (int j = i; j <= r[a[i]]; ++j) {
      if (j == l[a[j]]) {
        fen.modify(a[j], dp[l[a[j]]]);
      }
    }
    i = r[a[i]];
  }
  cout << set<int>(a.begin(), a.end()).size() - *max_element(dp.begin(), dp.end()) << "\n";
}

D - 距离之比(推公式+贪心)

分析

\(\bigtriangleup x = x_1 - x_2, \bigtriangleup y = y_1 - y_2\),对原公式进行平方并化简可得:\(1 + {\frac 1 {\frac {\bigtriangleup x} {\bigtriangleup y} + \frac {\bigtriangleup x} {\bigtriangleup y}}}\)。想让这个式子尽可能的大,也就是说让\({\frac {\bigtriangleup x} {\bigtriangleup y} + \frac {\bigtriangleup x} {\bigtriangleup y}}\)尽量取小。设\(tanθ = \frac {\bigtriangleup y} {\bigtriangleup x}\), 此时式子变化为\(tanθ + \frac 1 {tanθ}\), 可以发现其在\(θ\)\(45^\circ\)\(135^\circ\)时取到最优。因此只要对\(x + y\)\(x - y\)分别进行排序,此时两两相邻为最优,两者取个最大值即可。

代码实现

#include <bits/stdc++.h>
int main() {
    std::cin.tie(nullptr)->sync_with_stdio(false);
    std::cout << std::fixed << " " << std::setprecision(20);
    #define int long long
    int tt;
    for (std::cin >> tt; tt--;) {
        int n;
        std::cin >> n;
        std::vector<std::pair<int, int>> p(n);
        for (auto &[x, y] : p) {
            std::cin >> x >> y;
        }
        std::sort(p.begin(), p.end(), 
            [&](const auto& a, const auto& b) {
                return a.first + a.second < b.first + b.second;
            });
        double ans = 0;
        for (int i = 1; i < n; ++i) {
            int d1 = p[i - 1].first - p[i].first, d2 = p[i - 1].second - p[i].second;
            ans = std::max<double>(ans, ((abs(d1) + abs(d2)) * 1. / sqrt(d1 * d1 + d2 * d2)));
        }
        std::sort(p.begin(), p.end(), 
            [&](const auto& a, const auto& b) {
                return a.first - a.second < b.first - b.second;
            });
        for (int i = 1; i < n; ++i) {
            int d1 = p[i - 1].first - p[i].first, d2 = p[i - 1].second - p[i].second;
            ans = std::max<double>(ans, ((abs(d1) + abs(d2)) * 1. / sqrt(d1 * d1 + d2 * d2)));
        }
        std::cout << ans << '\n';
    }
}

E - 保卫城邦

分析

太菜了,不会写

F - 优秀字符串 (模拟)

分析

按照题意模拟即可

代码实现

#include <bits/stdc++.h>
int main() {
    std::cin.tie(nullptr)->sync_with_stdio(false);
    int n;
    std::cin >> n;
    int ans = 0;
    for (int i = 0; i < n; ++i) {
        std::string s;
        std::cin >> s;
        if (size(s) == 5 && s[2] == s[4] && std::set<char>{s[0], s[1], s[2], s[3]}.size() == 4) {
            ans += 1;
        }
    }
    std::cout << ans << '\n';
}

G - 扫雷 2 (构造)

分析

原来扫雷2的2是这个2щ(ಠ益ಠщ)
想了有两种构造方法:

第一种方法:
对于\(n \leq 2 * m\)时,我们可以直接按照如下方法构造
\(n = 5, m = 5\)   \(n = 5, m = 6\)
01100        01100
11000        11000
10000        10000
00000        00000
00000        00001
此时对于m为奇数直接在左上角构造两个斜杠,为偶数时多出来的可以直接放在右下角。(注意特判\(n=5,m=10\)的情况)
此时对于\(n > 2 * m\)的情况我们考虑缩小矩阵,可将雷埋在最下面一行和最右边一行,此时可以得到\(n - 1\)的矩阵,例如 \(n = 6, m = 25\)时可以构造如下矩阵:
011011
110011
100011
000011
111111
111111
此时问题回到\(n \leq 2 * m\)的情况,注意构造过程中的细节即可。

第二种方法(感觉可行,但是并没有能写出来):
构造一个匚字型,形如:
\(n = 5, m = 7\)   \(n = 5, m = 14\)
11100        11110
01000        10100
11100        10100
01000        10100
00000        11110
按照以上样子构造即可,构造完后多出来的雷往中间空出来的地方埋就行了,最后注意特判m较小的情况。(其实感觉可以方法一方法二结合写起来更简单一些)

代码实现(方法1)

#include <bits/stdc++.h>
int main() {
    std::cin.tie(nullptr)->sync_with_stdio(false);
    auto bury = [&](std::vector<std::string>& adj, int num, int k) {
        int len = (num + 1) / 2;
        for (int i = 0; i < len; ++i) {
            adj[i][len - i - 1]++;
        }
        for (int i = 0; i < len - 1; ++i) {
            adj[i][len - i - 2]++;
        }
        adj[k][k] += num % 2 == 0;
    };
    int tt;
    for (std::cin >> tt; tt--;) {
        int n, m;
        std::cin >> n >> m;
        std::vector<std::string> ans(n, std::string(n, '0'));
        if (m <= 2 * n) {
            if (n == 5 && m == 10) {
                for (int i = 0; i < n; ++i) {
                    for (int j = 0; j < i; ++j) {
                        ans[j][n - i - 1]++;
                    }
                }
            } else bury(ans, m, n - 1);
        } else {
            int now = n;
            while (m >= 2 * now + 1 && now) {
                m -= 2 * now - 1;
                now--;
                for (int i = 0; i < now; ++i) {
                    ans[i][now]++, ans[now][i]++;
                }
                ans[now][now]++;
            }
            if (m >= 2 && m < 2 * now - 1) {
                ans[0][now - 1]++, ans[now - 1][0]++;
                m -= 2;
            }
            if (m) {
                if (now == 5 && m == 10) {
                    for (int i = 0; i < now; ++i) {
                        for (int j = 0; j < i; ++j) {
                            ans[j][now - i - 1]++;
                        }
                    }
                } else if (now == 3 && m == 1) {
                    ans[now - 1][now - 1]++;
                } else if (now == 4 && m == 8) {
                    bury(ans, m, now - 1);
                    ans[0][0]++, ans[now - 1][now - 1]--;
                } else {
                    if ((now == 4 && (m == 3 || m == 4)) || (now == 3 && m == 2) || (now == 5 && m == 5)) {
                        ans[0][now - 1]--, ans[now - 1][0]--;
                        if (now == 4 && m == 4) {
                            ans[now - 1][now - 1]--;
                            ans[0][0]++;
                        }
                        m += 2;
                    }
                    if (now >= 5) {
                        if (now - (m + 1) / 2 == 2) {
                            ans[now - 2][now - 1]++, ans[now - 1][now - 2]++;
                            m -= 2;
                        }
                        if ((m & 1) && !(now == 5 && m == 9)) {
                            m += 1;
                            ans[n - 1][n - 1]--;
                        }
                    }
                    if (m) {
                        bury(ans, m, now - 1);
                    }
                }
            } else if (now == 3 || now == 2) {
                ans[0][now - 1]--, ans[now - 1][0]--;
                ans[1][1]++, ans[0 + 2 * (now == 3)][0 + 2 * (now == 3)]++;
            }
        }
        std::cout << "Yes" << '\n';
        for (int i = 0; i < n; ++i) {
            std::cout << ans[i] << "\n";
        }
    }
}

H - 随机栈

分析

队友写的,题也没有看,贴一下队友代码和思路:
这题实际上是求这个随机栈的出栈序列是非降序的概率,然后求逆元就行。那么我们只需要求出这个概率的分子,分母,最后通过费马小定理用快速幂求出逆元就行
\(fenzi * ksm(fenmu , mod - 2)\)
求分子分母的过程就是利用古典概型分别求出总共的情况数(即分母),非降序的情况数(即分子)。
求分母的过程就是每次入栈 -1 时要乘上当前栈的大小,就能算出总共的情况数;
求分子的过程就是每次入栈 -1 时要乘上当前栈顶元素的个数,表示这些元素都能达到出栈序列非降序。
特别的当无法达成出栈序列是非降序时,概率为0,将分子置0就行

代码实现

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr ll mod = 998244353;

//快速幂
constexpr int P = 998244353; 
ll ksm(ll a , ll b) {
    ll res = 1;
    while (b) {
        if (b & 1) res = (ll)res * a % P;
        a = (ll)a * a % P; 
        b >>= 1;
    }
    return res;
}
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);

    ll n , pren = 0 , fenzi = 1 , fenmu = 1;
    cin >> n;
    n *= 2;
    priority_queue <ll, vector<ll>, greater<ll>> ql;
    vector<ll> a(n+2);
    map<ll , ll> mp; 
    for (int i=1; i <= n; ++ i) {
        cin >> a[i];
        if (a[i] == -1) {
            ll len = (ll)ql.size();
            fenmu = fenmu % mod * len % mod;
            fenzi = fenzi % mod * mp[ql.top()] % mod;
            pren = max(ql.top() , pren);
            mp[ql.top()]--;
            ql.pop();
        } else if (a[i] != -1) {
            ql.push(a[i]);
            mp[a[i]] ++;
            if (a[i] < pren) {
                fenzi = 0;
            }
        }
    } 
    ll ans = fenzi * ksm(fenmu , mod - 2) % mod;
    std::cout << ans << "\n";
}

I - 378QAQ 和字符串 (枚举+hash+二分)

分析

枚举p,每次从p到n开始查找,每次跳过\(i\)\(i-p\)的lcp,以i % p为起点,统计一下以此起点时需要修改的数量,并且标记一下i % p已经被修改过了,最后判断一下以p为循环节需要修改的数量是否小于k即可。(很极限的创过去了,队友还在想着如何用卷积卷过Test4 “v(〓 ̄(∵エ∵) ̄〓)v”)

代码实现

#include <bits/stdc++.h>
using ULL = unsigned long long;
int main() {
    std::cin.tie(nullptr)->sync_with_stdio(false);
    int tt;
    for (std::cin >> tt; tt--;)[&] {
        int n, k;
        std::cin >> n >> k;
        std::string s;
        std::cin >> s;
        constexpr int P = 131;
        std::vector<ULL> h(n + 1), p(n + 1);
        p[0] = 1;
        for (int i = 0; i < n; ++i) {
            h[i + 1] = h[i] * P + s[i];
            p[i + 1] = p[i] * P; 
        }
        auto get = [&](int l, int r) { 
            return h[r] - h[l - 1] * p[r - l + 1];
        };
        auto lcp = [&](int a, int b) {
            int l = 0, r = n - b;
            while (l < r) {
                int mid = l + r >> 1;
                if (get(a + 1, a + 1 + mid) == get(b + 1, b + 1 + mid)) {
                    l = mid + 1;
                } else {
                    r = mid;
                }
            }
            return r;
        };
        std::vector<int> vis(n);
        for (int p = n / 4 + 1; p <= n / 2; ++p) {
            int cost = 0;
            for (int i = p; i < n && cost <= k; ++i) {
                i += lcp(i - p, i);
                if (i == n) break;
                int x = i % p;
                if (vis[x] != p) {
                    vis[x] = p;
                    std::vector<int> cnt(26);
                    int res = 0, tot = 0;
                    for (int j = x; j < n; j += p) {
                        tot += 1;
                        res = std::max(res, ++cnt[s[j] - 'a']);
                    }
                    cost += tot - res;
                }
            }
            if (cost <= k) {
                std::cout << "Yes" << '\n';
                return ;
            }
        }
        std::cout << "No" << '\n';
    } ();
}

J - 排列与合数 (全排列)

分析

长度只有5,全排列暴力枚举即可,最后代码队友写的

代码实现

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
//线性筛
constexpr int maxn = 1e6 + 10; 
int pnl[maxn + 10], cnt;//pnl
int st[maxn + 10];//索引为质数值就是0
void init_primes() {
    st[0]=1;
    st[1]=1;
    for (int i=2; i <= maxn; ++i) {
        if (st[i] == 0){
            pnl[cnt++] = i;
        }
        for (int j=0; pnl[j] <= maxn/i; ++j){
            st[pnl[j]*i] = 1;
            if (i%pnl[j] == 0) {
                break;
            }
        }
    }
}
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    
    init_primes();

    int tt = 1;
    cin >> tt;
    while (tt--) [&]{
        int n , res;
        cin >> n;
        vector<int> cur(5);
        for (int j=4; j>=0; -- j) { 
            cur[j] = n % 10;
            n /= 10;
        }
        sort(cur.begin(), cur.end());
        do {
            int nn = 0;
            for (int j=0; j < 5; ++ j) {
                nn = nn * 10 + cur[j];
            }
            if (1e4 <= nn && st[nn] == 1) {
                std::cout << nn << '\n';
                return ;
            }
        } while (next_permutation(cur.begin() , cur.end()));
        std::cout << -1 << '\n';
    }();
}

K - 树上问题 (并查集)

分析

可以将能够互为父子的视为一个集合,随后对集合之间连边,u->v代表u可以做v的父亲节点,最后找到唯一的根节点集合(唯一一个没有入度的集合,有多个则无解)即可。

代码实现

#include <bits/stdc++.h>
struct DSU {
    std::vector<int> p, siz;
    DSU(int n) : p(n), siz(n, 1) { std::iota(p.begin(), p.end(), 0); }
    int leader(int x) {
        while (x != p[x]) x = p[x] = p[p[x]];
        return x;
    }
    bool same(int x, int y) { return leader(x) == leader(y); }
    bool merge(int x, int y) {
        x = leader(x), y = leader(y);
        if (x == y) return false;
        siz[x] += siz[y], p[y] = x;
        return true;
    }
    int size(int x) { return siz[leader(x)]; }
};
int main() {
    std::cin.tie(nullptr)->sync_with_stdio(false);
    int tt;
    for (std::cin >> tt; tt--;) {
        int n;
        std::cin >> n;
        std::vector<int> val(n);
        for (int i = 0; i < n; ++i) {
            std::cin >> val[i];
        }
        DSU dsu(n);
        std::vector<std::pair<int, int>> edges;
        for (int i = 0; i < n - 1; ++i) {
            int a, b;
            std::cin >> a >> b;
            a--, b--;
            int num = (val[a] * 2 >= val[b]) + (val[b] * 2 >= val[a]);
            if (num == 2) {
                dsu.merge(a, b);
            } else {
                if (val[a] * 2 <= val[b]) {
                    edges.emplace_back(a, b);
                } else {
                    edges.emplace_back(b, a);
                }
            }
        } 
        std::vector<int> d(n);
        std::vector<bool> use(n);
        for (auto [x, y] : edges) {
            x = dsu.leader(x), y = dsu.leader(y);
            use[x] = use[y] = true;
            d[y] += 1;
        }
        if (dsu.size(0) == n) {
            std::cout << n << '\n';
        } else {
            int root = -1, num = 0;
            for (int i = 0; i < n; ++i) {
                if (d[i] == 0 && use[i]) {
                    root = i;
                    num += 1;
                } 
            }
            if (num == 1) {
                std::cout << dsu.size(root) << '\n';
            } else {
                std::cout << 0 << '\n';
            }
        }
    }
}

L - Toxel 与 PCPC II(dp)

分析

依旧队友神力,并没有读题的机会

代码实现

#include <bits/stdc++.h>
using namespace std;
int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
  #define int long long
  int n, m;
  cin >> n >> m;
  vector<int> a(m);
  for (int i = 0; i < m; ++i) {
    cin >> a[i];
  }
  auto power = [&](int x) {
    return x * x * x * x;
  };
  vector<int> dp(m + 1, 0);
  for (int i = 1; i <= m; ++i) {
    dp[i] = 2e18;
    for (int j = max<int>(0, i - 400); j < i; ++j) {
      dp[i] = min<int>(dp[i] , dp[j] + a[i - 1] + power(i - j));
    }
  }
  cout << dp[m] << '\n';
}

M - 有效算法 (二分)

分析

可以发现\(k\)越大,\(x\)可取的范围越大,因此直接二分\(k\)的最小值,判断所有\(b_i * k\)可以取到的\(x\)是否有公共区间即可。

代码实现

#include <bits/stdc++.h>
int main() {
    std::cin.tie(nullptr)->sync_with_stdio(false);
    #define int long long
    int tt;
    for (std::cin >> tt; tt--;) {
        int n;
        std::cin >> n;
        std::vector<int> a(n), b(n);
        for (int i = 0; i < n; ++i) {
            std::cin >> a[i];
        }
        for (int i = 0; i < n; ++i) {
            std::cin >> b[i];
        }
        auto check = [&](int k) {
            int x = k * b[0];
            int L = a[0] - x, R = a[0] + x;
            for (int i = 1; i < n; ++i) {
                x = k * b[i];
                int l = a[i] - x, r = a[i] + x;
                if (r < L || l > R) {
                    return false;
                } else {
                    L = std::max(L, l);
                    R = std::min(R, r);
                }
            }
            return true;
        };
        int l = 0 , r = 1e9;
        while(l < r) {
            int mid = l + r >> 1;
            if (check(mid)) {
                r = mid;
            } else {
                l = mid + 1;
            }
        }
        std::cout << r << '\n';
    }
}
posted @ 2024-05-16 11:29  sleeeeeping  阅读(508)  评论(1编辑  收藏  举报