• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • YouClaw
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

RomanLin

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

【划分型贪心+因式分解】codeforces 1992 F. Valuable Cards

题目

https://codeforces.com/contest/1992/problem/F

题意

输入一个正整数 \(T(1 \leq T \leq 10^3)\),表示一共 \(T\) 组测试用例,保证所有测试用例的 \(n\) 之和不超过 \(10^5\)。对于每个测试用例:
第一行输入两个正整数 \(n\) 和 \(x(1 \leq n \leq 10^5,2 \leq x \leq 10^5)\);
第二行输入一个长为 \(n\) 的整数数组 \(a\),其中 \(a_i(1 \leq a_i \leq 2 \times 10^5, a_i != x)\) 代表第 \(i\) 个数的值。

若一个连续的子数组中,存在一个或以上的子序列的乘积为 \(x\),则称该子数组为坏段。问最少将数组 \(a\) 分为多少个不重叠的子数组,可使得每个子数组都不是坏段。输出最少需要分段的数量。

题解

很容易想到的一个划分型贪心思路是:从左往右遍历数组,当从下标位置 \(i\) 遍历到下标位置 \(j(j > i)\) 时,若子数组 \(a_{i..j}\) 已经存在一个子序列的乘积为 \(x\),但从下标位置 \(i\) 遍历到下标位置 \(j-1\) 时,子数组 \(a_{i..(j-1)}\) 还不存在一个子序列的乘积为 \(x\),那么需要在下标位置 \((j-1)\) 与下标位置 \(j\) 之间明显需要隔开。然后从下标位置 \(j\) 重复上述思路,直至遍历完数组。

于是,问题就转化为如何快速判断子数组 \(a_{i..j}\) 是否存在一个子序列的乘积为 \(x\)。

不妨假设 \(x = p \times q \times r\) 是 \(x\) 的一种因式分解的方式,那么显然成立的是:

  • \(p \text{ | } x\)
  • \(q \text{ | } x\)
  • \(r \text{ | } x\)

亦即,若子数组 \(a_{i..j}\) 中存在一个子序列的乘积为 \(x\),那么这个子序列的每个元素都必定是 \(x\) 的因子。那么,若数组元素不是 \(x\) 的因子则可以直接跳过;若数组元素是 \(x\) 的因子,则需要进行以下思考:

不妨仍以 \(x = p \times q \times r\) 为例进行讲解:
首先要理清一个思路,为什么当从下标位置 \((j-1)\) 遍历到下标位置 \(j\) 时就需要隔开了?
理由就是因为在引入 \(a_j\) 以后,就存在了至少一个包含元素 \(a_j\) 的子序列的乘积为 \(x\)。
不妨假设当前遇到的数组元素 \(a_j\) 的值就是 \(p\),那么这意味着:
1.\(a_j \text{ | } x\),即 \(p \text{ | } x\)
2.在子数组 \(a_{i..(j-1)}\) 中已经存在至少一个子序列的乘积为 \(\frac{x}{a_j}\)

因此,我们可以得到的一个基本思路是:判断引入 \(a_j\) 以后,再碰到什么数即可凑出 \(x\),这可以用一个集合 \(S\) 维护。那么在数组遍历过程中,若元素 \(a_j\) 不是 \(x\) 的因子,则直接跳过,否则判断集合 \(S\) 中是否有 \(a_j\),若有则直接判定当前位置需要隔开,否则就用集合 \(S\) 中的每个数除以 \(a_j\),若结果大于 \(0\),可以加入到集合 \(S\) 中(需要注意的是,要在遍历完集合以后再加入,否则会引发 ub)。

参考代码

方法一:使用两个 set 集合维护(编码简单,效率较低)

#include<bits/stdc++.h>
using namespace std;

int T, n, x;
int a[200005];

void solve() {
    int ans = 1;
    cin >> n >> x;
    set<int> st1 = {x}, st2;
    for (int i = 0; i < n; ++ i) {
        cin >> a[i];
        if (x % a[i]) continue;
        if (st1.find(a[i]) != st1.end()) {
            ++ ans;
            st1 = {x, x / a[i]};
        } else {
            for (auto &it: st1) if (it % a[i] == 0) st2.insert(it / a[i]);
            st1.merge(st2);
            st2.clear();
        }
    }
    cout << ans << '\n';
}

int main() {
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    cin >> T;
    while (T --) solve();
    return 0;
}

方法二:栈(编码略微复杂,效率高)

#include<bits/stdc++.h>
using namespace std;

constexpr int N = 200005;
int T, n, x;
int a[N];
int d[N];
bool vis[N];

void solve() {
    int ans = 1, m = 0;
    cin >> n >> x;
    stack<int> stk;
    vis[x] = true;
    d[m ++] = x;
    for (int i = 0; i < n; ++ i) {
        cin >> a[i];
        if (x % a[i]) continue;
        if (vis[a[i]]) {
            ++ ans;
            m = 1;
            while (!stk.empty()) vis[stk.top()] = false, stk.pop();
        }
        for (int j = 0, k = m; j < k; ++ j)
            if (d[j] % a[i] == 0 && !vis[d[j] / a[i]]) {
                d[m ++] = d[j] / a[i];
                vis[d[j] / a[i]] = true;
                stk.push(d[j] / a[i]);
            }
    }
    while (!stk.empty()) vis[stk.top()] = false, stk.pop();
    vis[x] = false;
    cout << ans << '\n';
}

int main() {
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    cin >> T;
    while (T --) solve();
    return 0;
}

posted on 2026-04-05 22:44  RomanLin  阅读(4)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3