2024 CCPC 郑州题解 (gym105158)

A

其实是构造。

构造出 123456789d000……。 后面 0 的个数跟给定 n 的位数相同。

然后加上 n 减模数就好了

CODE
void solve() {
       int n,d;
       cin>>n>>d;
       i64 cur=1234567890ll+d;
       int nn=n;
       while(nn){
           cur=cur*10;
           nn=nn/10;
       }
       i64 k=cur%n;
       k=n-k;
       cur+=k;
       i64 ans=cur/n;
       cout<<ans<<'\n';
}

B

贪心,对于每轮,将买过的所有比当前轮贵的(用优先队列维护)都放弃掉来买当前轮的,然后剩余的钱如果足够的话也都买当前轮的。

CODE
void solve()
{
    int n = 0;
    std::cin >> n;
    std::priority_queue<std::pair<int, int>> q;
    for (int i = 0, cnt = 1; i < n; i++, cnt++) {
        int a = 0;
        std::cin >> a;
        std::pair<int, int> tmp{ a, 0 };
        while (!q.empty() && q.top().first > a) {
            int cnt2 = q.top().first * q.top().second;
            q.pop();
            
            tmp.second += cnt2 / a;
            cnt += cnt2 % a;
        }
        tmp.second += cnt / a;
        cnt %= a;
        if (tmp.second >= 1)
            q.push(tmp);
    }
    int ans = 0;
    while (not q.empty()) {
        ans += q.top().second;
        q.pop();
    }

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

C

如果所有的数都不相同,那直接总长度 - 最长上升子序列的长度就是我们要的答案。因为我们要尽可能改变少的数。

如果有相同的数,那么两个相同的数之间的数得和这个数一起变化,即对于 x a1 a2 a3 a4 x\(f(ai) = f(x)\),我们可以把这些看成一块。每一块(单独一个数单独看成一块)对于最长上升序列的长度的贡献最多是 1,为了保证这一点,我们将每一块块内单独降序排序。然后对排好序的序列求最长上升子序列的长度。最后的答案就是序列中不同数的个数减去这个最长上升子序列的长度。

CODE
void solve() {
     int n = 0;
     std::cin >> n;
     std::vector a(n, 0);
     std::vector lst(n + 1, -1);
     int cnt = 0;
     for (int i = 0; i < n; i++) {
         std::cin >> a[i];
          if (lst[a[i]] == -1) {
               cnt++;
          }
          lst[a[i]] = i;
     }

     std::vector vis(n + 1, false);
     for (int i = 0; i < n; i++) {
          if (vis[i]) {
               continue;
          }
          int r = lst[a[i]];
          for (int j = i; j <= r; j++) {
               vis[j] = true;
               r = std::max(r, lst[a[j]]);
          }

          std::sort(a.begin() + i, a.begin() + r + 1, std::greater<int>());
     }

     std::vector dp(n + 1, Inf);
     dp[0] = 0;
     for (int i = 0; i < n; i++) {
          *std::lower_bound(dp.begin(), dp.end(), a[i]) = a[i];
     }
     int len = n;
     while (dp[len] == Inf) {
          len--;
     }
     std::cout << cnt - len << '\n';
}

D

将式子化简我们就得到了我们要找的其实是使 \(\sqrt{2}\sin({\pi \over 4} + \theta)\),其中 \(\theta\) 是两点连线的倾斜角,也就是最接近 \(\pi \over 4\)\(7\pi \over 4\) 的倾斜角。其实后者可以通过将整个图顺时针旋转 \(\pi \over 2\) 得到。我们只用找到里 \(\pi \over 4\) 最近的倾斜角就好了。

对于任意点 \((x, y)\),我们都有 \(y = x + d\),也就是说我们可以按截距将所有的点分类,分类后如果存在某一类有多个点,则我们就可确定倾斜角能够达到 \(\pi \over 4\) 我们就可以直接返回最优答案 \(\sqrt{2}\) 了。否则将所有点按照截距大小排序,最优答案一定是出现在相邻的两个点上的。这个证明画画三角形就可以了(三角形上至少有两个点是相邻的)。

CODE
double sqr2 = 1.4142135623730951;

double solve(std::vector<std::pair<i64, i64>> b) {
     std::unordered_set<i64> s;
     for (auto &[x, y] : b) {
          if (s.count(y - x)) {
               return sqr2;
          }
          s.insert(y - x);
     }

     std::sort(b.begin(), b.end(), [](const std::pair<i64, i64> &a, const std::pair<i64, i64> &b) {
          return a.second - a.first < b.second - b.first;
     });
     
     double ans = 0;

     for (int i = 1; i < b.size(); i++) {
          // 两点所连直线的斜率
          if (b[i].second == b[i - 1].second) {
               ans = std::max(ans, 1.0);
               continue;
          }
          double k = (b[i].first - b[i - 1].first) * 1.0 / (b[i].second - b[i - 1].second);
          ans = std::max(ans, sqr2 * std::sin(3.14159265358979323846 / 4.0 + std::atan(k)));
     }

     return ans;
}

int main()
{
     IOS;
     int _t = 1;
     std::cin >> _t;
    
     while (_t--) {
          int n = 0;
          std::cin >> n;
          std::vector<std::pair<i64, i64>> a(n);
          for (int i = 0; i < n; i++) {
               std::cin >> a[i].first >> a[i].second;
          }

          double ans = solve(a);
          for (int i = 0; i < n; i++) {
               std::swap(a[i].first, a[i].second);
               a[i].second = -a[i].second;
          }
          std::cout << std::fixed << std::setprecision(10) << std::max(ans, solve(a)) << '\n';
     }

     sp();

     return 0;
}

F

H

首先若要取出来的序列是不降的,则对于每次取出来的数都是确定的,我们只需要看目前从随机栈中取出当前应该取出的数的概率是多少。

CODE
void solve()
{
    int n = 0;
    std::cin >> n;
    std::vector a(2 * n, 0), b(0, 0);
    for (auto &i : a) {
        std::cin >> i;
        if (i != -1) {
            b.push_back(i);
        }
    }
    std::sort(b.begin(), b.end());
    std::vector cnt(n + 1, 0);
    int idx = 0, sum = 0;
    i64 ans = 1;
    for (int i = 0; i < 2 * n; i++) {
        if (a[i] == -1) {
            if (cnt[b[idx]] == 0) {
                std::cout << 0 << '\n';
                return;
            }
            
            (ans *= cnt[b[idx]] * inv(sum) % Mod) %= Mod;
            cnt[b[idx]]--;
            idx++;
            sum--;
        }
        else {
            cnt[a[i]]++;
            sum++;
        }
    }
    std::cout << ans << '\n';
}

J

对于任意给出的数都可能组成合数,因为凡是以 0, 2, 4, 6, 8, 5 这 6 个数之一结尾的五位数一定是合数,而题目给出了 5 个不同的数,一定至少包含上面的一个。最后注意一下 0 不要放在最前面就好了。

K

从节点 \(u\) 来看我们定义三种边:

  1. \(u -> v\) 表示 \(u\) 能作为 \(v\) 的父节点,反之不然。
  2. \(u <- v\) 表示 \(v\) 能作为 \(u\) 的父节点,反之不然。
  3. \(u -- v\) 两者谁做父亲都可以。

我们按以下条件 dfs 整个树:

  1. 不走第 1. 种边,
  2. 不走第 2. 种边,且记录碰到了几次第 2. 种边
  3. 要记录走过了多少点。

按照如上规则,我们 dfs 时会将整颗树分成若干块,并且我们还知道每块碰到了多少次第 2. 种边。如果任意一块碰到了两次及以上的第 2. 种边,整个图就是无解的。因为一个块碰到第 2. 种边意味着美丽节点只能在这一块之外的某一块,而碰到多个第 2. 种边就会产生矛盾,一个第 2. 种边说美丽节点只能在块 \(x\),另一个又说美丽节点只能在块 \(y\),由于图是树,所以 \(x \not = y\)。如果一个块没有碰到第 2. 种节点,那么这块的节点数就有可能是答案(因为还要检查是否有产生矛盾的块)。

CODE
std::vector<int> g[N + 5], a(N + 1, 0);
bool vis[N + 5];

std::array<int, 2> dfs(int cur, int fa) {
     std::array<int, 2> res = { 1, 0 };
     vis[cur] = true;
     for (auto &to : g[cur]) {
          if (to == fa || 2 * a[cur] < a[to]) {
               continue;
          }

          if (2 * a[to] < a[cur]) {
               res[1]++;
          }
          else {
               auto [cnt, sum] = dfs(to, cur);
               res[0] += cnt;
               res[1] += sum;
          }
     } 
     return res;
}

void solve()
{
     int n = 0;
     std::cin >> n;
     for (int i = 1; i <= n; i++) {
          std::cin >> a[i];
          g[i].clear();
          vis[i] = false;
     }
     for (int i = 1; i < n; i++) {
          int u = 0, v = 0;
          std::cin >> u >> v;
          g[u].push_back(v);
          g[v].push_back(u);
     }

     int ans = 0;
     bool ok = true;
     for (int i = 1; i <= n; i++) {
          if (not vis[i]) {
               auto [cnt, sum] = dfs(i, 0);
               if (sum >= 2) {
                    ok = false;
                    break;
               }
               if (sum == 0) {
                    ans = cnt;
               }
          }
     }

     std::cout << (ok ? ans : 0) << '\n';
}

L

我们最多同时解决 21 个问题,因为 \(22^4\) 就已经大于 \(2 \times 10^5\) 了,此时我还不如直接单独解决最远的那个问题。所以我们就可以dp 了,对于每一处 bug,枚举他和前面的多少个 bug 是一起解决的。

CODE
void solve() {
       int n,m;
       cin>>n>>m;
       vector<int>a(m+1);
       for(int i=1;i<=m;i++) cin>>a[i];
       vector<int>b(22,0);
       for(int i=1;i<=21;i++) b[i]=pow(i,4);
       int mx=m;
       for(int i=1;i<=m;i++) mx+=a[i];

       vector<int> dp(m+1,mx);
       dp[0]=0;
       dp[1]=1+a[1];
       for(int i=2;i<=m;i++){
          for(int j=1;j<=std::min(21ll, i);j++){
               dp[i]=min(dp[i],dp[i-j]+b[j]+a[i]);
          }
       }
       cout<<dp[m]<<"\n";
}

M

二分 K,然后求交集就好了。

CODE
void solve()
{
    int n = 0;
    std::cin >> n;
    std::vector a(n, 0ll), b(n, 0ll);
    for (auto &i : a) {
        std::cin >> i;
    }
    for (auto &i : b) {
        std::cin >> i;
    }

    i64 l = 0, r = Inf;
    while (l <= r) {
        i64 m = l + r >> 1;
        i64 L = 0, R = 2ll * Inf;
        for (int i = 0; i < n; i++) {
            L = std::max(L, a[i] - m * b[i]);
            R = std::min(R, a[i] + m * b[i]);
        }

        if (L <= R) {
            r = m - 1;
        }
        else {
            l = m + 1;
        }
    }
    std::cout << l << '\n';
    return;
}
posted @ 2025-03-06 20:53  Young_Cloud  阅读(267)  评论(0)    收藏  举报