【论题选编】可重集表示数问题

it2025-04-17  5

(最近做题太少,只能水一个简单的文章。)

大概就是三个题目混合起来。

首先给出定义:一个可重集 S S S 可以表示出数 x x x,当且仅当存在 T ⊂ S T\subset S TS 使得 T T T 中元素的和恰为 x x x

例 1

题意:给定数组 a a a 和数 n n n,问最少向 a a a 中插入多少个在 [ 1 , n ] [1, n] [1,n] 中的正整数,能让 a a a 构成的可重集表示出 x x x。(来源:Leetcode 330)

贪心。

一个很显然的想法是:如果当前数组覆盖不到 x x x,那么我们肯定要添加一个 ≤ x \le x x 的数到数组中去。我们现在就考虑怎么把这个数填上,让它产生的效果尽量好。

我们假设当前可以覆盖到的区间是 [ 1 , x ) [1, x) [1,x),那么如果把 y ≤ x y\le x yx 加到数组中,那么整个数组就可以多覆盖 y , y + 1 , ⋯   , y + x − 1 y, y+1, \cdots, y+x-1 y,y+1,,y+x1 这些数,换言之,覆盖范围就多了 [ y , y + x ) [y, y+x) [y,y+x) 这一块。由于 y ≤ x y \le x yx,故两个区间可以合并为 [ 1 , y + x ) [1, y+x) [1,y+x)。那么显然,如果要加入的话当然是加入 x x x 可以使得覆盖范围增加的最多。这就回答了我们之前的问题。

这样算法的雏形就出来了:我们维护这样一个区间,记录最小的无法覆盖到的数 x x x。一开始设 x = 1 x=1 x=1。然后从小到大用 a a a 中的值去更新 x x x,如果当前的值大于 x x x 就表明一定要新加入一个数,由上一段的结论可知加入 x x x 是最好的。如此反复,直到全部覆盖为止。


例 1 中说明了如何利用贪心思想确定一个可重集 S S S 能否表示出 x x x。很容易将其扩展到区间询问上。

例 2

题意:给定长为 n n n 的序列 a a a m m m 次询问,每次给出 l , r l, r l,r,问 a [ l : r ] a[l: r] a[l:r] 构成的可重集无法表示的最小非负整数是多少。(来源:FJOI 2016 神秘数)

方法一

主席树?

方法二

建线段树,对每个点维护一个值 x x x 和一个有序 pair 列表,表示这个点对应的区间做完合并后,可以覆盖的区间为 [ 0 , x ) [0, x) [0,x),且剩余的待合并的数为 ( y 1 , z 1 ) , ( y 2 , z 2 ) , ⋯ (y_1, z_1), (y_2, z_2), \cdots (y1,z1),(y2,z2),

这个列表的意义是:如果 x x x 增大到某个不小于 y 1 y_1 y1 x ′ x' x,那么新的可覆盖区间会变成 [ 0 , x ′ + y 1 + z 1 ) [0, x' + y_1 + z_1) [0,x+y1+z1),但这时仍然有 y 2 > x ′ + y 1 + z 1 y_2 > x' + y_1 + z_1 y2>x+y1+z1

对于 ( y 2 , z 2 ) (y_2, z_2) (y2,z2) 的定义类似:如果 x ′ + y 1 + z 1 x' + y_1 + z_1 x+y1+z1 增大到某个不小于 y 2 y_2 y2 x ′ ′ x'' x,那么新的可覆盖区间会变成 [ 0 , x ′ ′ + y 2 + z 2 ) [0, x'' + y_2 + z_2) [0,x+y2+z2),但这时仍然有 y 3 > x ′ ′ + y 2 + z 2 y_3 > x'' + y_2 + z_2 y3>x+y2+z2。对于 ( y 3 , z 3 ) (y_3, z_3) (y3,z3) 之类的可以类似定义下去。

这样定义之后,容易发现一个列表的长度至多是 log ⁡ 1 0 9 \log {10^9} log109,因为 y y y 是两倍两倍增长的。而合并两个点的信息也只需要归并两个列表的信息。因此总的时间复杂度为 O ( ( n + m ) log ⁡ n log ⁡ ( max ⁡ i a i ) ) O((n+m)\log n \log (\max_{i} a_i)) O((n+m)lognlog(maxiai))

例 3

题意:在例 2 的基础上加入单点修改操作。(来源:2019 ICPC 徐州区域赛 H 题)

直接在例 2 的方法二上进行稍许修改就能得到解法,代码如下。

#include <bits/stdc++.h> #define REP(temp, init_val, end_val) for (int temp = init_val; temp <= end_val; ++temp) #define REPR(temp, init_val, end_val) for (int temp = init_val; temp >= end_val; --temp) using namespace std; typedef long long ll; typedef pair<ll, ll> intpair; int read(){ int f = 1, x = 0; char c = getchar(); while (c < '0' || c > '9'){if(c == '-') f = -f; c = getchar();} while (c >= '0' && c <= '9')x = x * 10 + c - '0', c = getchar(); return f * x; } int n, m; int a[200005], siz; ll seg1[526000] = {0}, tmpv; vector<intpair > seg2[526000], tmp, tmp2; ll mmerge(ll xx1, ll xx2, vector<intpair >& v1, vector<intpair >& v2, vector<intpair >& res){ ll newx = xx1 + xx2 - 1; int siz1 = v1.size(), siz2 = v2.size(); int lp = 0, rp = 0; while (lp < siz1 || rp < siz2){ if (lp == siz1 || (rp < siz2 && v2[rp].first < v1[lp].first)){ // rp if (v2[rp].first <= newx) newx += v2[rp].first + v2[rp].second; else { if (res.empty() || v2[rp].first > 2ll * res.back().first + res.back().second) res.push_back(v2[rp]); else res.back().second += v2[rp].first + v2[rp].second; } ++rp; } else { if (v1[lp].first <= newx) newx += v1[lp].first + v1[lp].second; else { if (res.empty() || v1[lp].first > 2ll * res.back().first + res.back().second) res.push_back(v1[lp]); else res.back().second += v1[lp].first + v1[lp].second; } ++lp; } } return newx; } void init(){ n = read(), m = read(); REP(i, 1, n) a[i] = read(); for (siz = 1; siz < n; siz <<= 1) ; REP(i, 1, n) { if (a[i] == 1) seg1[i + siz - 1] = 2; else seg1[i + siz - 1] = 1, seg2[i + siz - 1].push_back(make_pair(a[i], 0)); } REP(i, siz + n, siz + siz - 1) seg1[i] = 1; REPR(i, siz - 1, 1){ seg1[i] = mmerge(seg1[i << 1], seg1[i << 1 | 1], seg2[i << 1], seg2[i << 1 | 1], seg2[i]); } } int _a, _b; void query(int id, int l, int r){ if (l > _b || r < _a) return ; if (l >= _a && r <= _b){ tmp2.clear(); tmpv = mmerge(tmpv, seg1[id], tmp, seg2[id], tmp2); tmp.clear(); for (const auto& p: tmp2) tmp.push_back(p); return ; } int mid = (l + r) >> 1; query(id << 1, l, mid); query(id << 1 | 1, mid + 1, r); } void solve(){ while (m--){ int opr = read(); _a = read(), _b = read(); if (opr == 1){ _a += siz - 1; seg2[_a].clear(); if (_b == 1) seg1[_a] = 2; else seg1[_a] = 1, seg2[_a].push_back(make_pair(_b, 0)); while (_a > 1) _a >>= 1, seg2[_a].clear(), seg2[_a] = mmerge(seg1[_a << 1], seg1[_a << 1 | 1], seg2[_a << 1], seg2[_a << 1 | 1], seg2[_a]); } else { tmpv = 1, tmp.clear(); query(1, 1, siz); printf("%lld\n", tmpv); } } } int main(){ init(); solve(); return 0; }

小结

这类问题是利用值域倍增的好例子,或许解决这类问题的方法能够推广到其他类似的值域倍增型题目上,从而避免去写主席树之类的复杂数据结构。

最新回复(0)