A. 小苯跑外卖
点击查看代码
void solve() {int x, y;std::cin >> x >> y;std::cout << (y + x - 1) / x << "\n";
}
B. 小苯的区间删除
任意次操作,把负数都删掉就行。
点击查看代码
void solve() {int n, k;std::cin >> n >> k;std::vector<int> a(n);for (int i = 0; i < n; ++ i) {std::cin >> a[i];}i64 ans = 0;for (int i = 0; i < n; ++ i) {ans += a[i] > 0 ? a[i] : 0;}std::cout << ans << "\n";
}
C. 小苯的数字消除
题意:给你一个\(01\)串,你每次可以选择两个相邻且相等的数同时删去。你可以更改任意位置,求使得最后串剩下的数小于等于1个的最小更改数。
先用栈模拟一遍,类似括号匹配,每次遇到相同的消去。
最后剩下的是一个01相间的串,形如\(010101..\)这种。这个串需要\(\frac{len}{2}\)次操作。
点击查看代码
void solve() {int n;std::cin >> n;std::string s;std::cin >> s;std::stack<char> stk;for (int i = 0; i < n; ++ i) {if (stk.size() && stk.top() == s[i]) {stk.pop();} else {stk.push(s[i]);}}int ans = (int)stk.size() / 2;std::cout << ans << "\n";
}
D. 小苯的数字集合
题意:有一个可重集合。一开始有两个数\(x, y\)。你每次可以选择集合的两个数,然后把它们的与值或者或值或者异或值或者\(gcd\)插入集合。求使得集合出现\(0\)的最少操作数。
首先我们最多需要三次操作,因为\(a \oplus b = c, c \oplus b = a, a \oplus a = 0\)。
那么我们分类讨论,一次操作的情况只有与操作和异或操作可以实现。
然后是两次操作的情况,我们把每个操作的值都插入,然后枚举它和\(x, y\)进行的操作看能不能得到\(0\)。
否则答案就是3。
点击查看代码
void solve() {int x, y;std::cin >> x >> y;//x^y=z//z^y=x//x^x=0if ((x & y) == 0 || (x ^ y) == 0) {std::cout << 1 << "\n";} else {std::vector<int> a{x & y, x | y, x ^ y, std::gcd(x, y)};for (auto & z : a) {if ((x & z) == 0 || (x | z) == 0 || (x ^ z) == 0 || (y & z) == 0 || (y | z) == 0 || (y ^ z) == 0) {std::cout << 2 << "\n";return;}}std::cout << 3 << "\n";}
}
E. 小苯的Polygon
题意:给你\(n\)个木棍,每个木棍有长度,你要用他们构造一个凸多边形,使得这个凸多边形的周长最小。
凸多边形的任意一条边长度都比其它\(n-1\)条边的长度之和小。
那么我们枚举最长的一条边,然后把比它小的边进行背包\(dp\)求出可以凑出的长度,取最接近它的。
点击查看代码
void solve() {int n;std::cin >> n;std::vector<int> a(n);for (int i = 0; i < n; ++ i) {std::cin >> a[i];}if (n < 3) {std::cout << -1 << "\n";return;} else if (n == 3) {if (a[0] + a[1] <= a[2] || a[0] + a[2] <= a[1] || a[1] + a[2] <= a[0]) {std::cout << -1 << "\n";} else {std::cout << a[0] + a[1] + a[2] << "\n";}} else {std::ranges::sort(a);int ans = -1;std::bitset<10010> b;b[0] = 1;for (int i = 0; i < n; ++ i) {if (i >= 2) {for (int j = a[i] + 1; j <= 10000; ++ j) {if (b[j]) {if (ans == -1 || a[i] + j < ans) {ans = a[i] + j;}break;}}}b |= b << a[i];} std::cout << ans << "\n";}
}
F. 小苯的线性dp
题意:给你一个数组,每次你可以进行一次合并操作,把两个数更换为它们的和。求分别在\([0, n - 1]\)操作后数组的最大极差。
首先我们想,合并的这些区间应该尽可能是一个区间,因为我们要让最大值最大,所以应该把数都合并到一起,不过也会出现操作两个区间的情况,这个下面会说。
我们可以用\(st\)表维护区间最大最小值,枚举操作的区间,那么假设这个区间的长度为\(len\),则\(ans[len - 1] = \min(ans[len - 1], \max(sum[i..j], left_{max}, right_{max}) - \min(left_{min}, right_{min})\)。
然后我们考虑操作了两个区间的情况,这种情况就是最小值和其它值相差太大,导致我们不能合并这个最小值,于是只能跨过它,进行一些无效操作。那么这意味着\(ans[i] = \max(ans[0], ans[1], .., ans[i - 1], ans[i])\)。但注意最后如果合并到只剩三个数,就是中间是最小值的情况,我们还要进行操作就只能合并它,所以\(ans[n - 2]\)不能和前面的取最大值。
不过\(ac\)后面发现其实我们求的是前后缀的最大最小值,似乎不需要\(st\)。
点击查看代码
const i64 inf = 1e18;template <class Info>
struct ST {std::vector<std::vector<Info>> st;ST(std::vector<Info> a) {int n = a.size(), m = std::__lg(n) + 1;st.assign(n, std::vector<Info>(m));for (int i = 0; i < n; ++ i) {st[i][0] = a[i];}for (int j = 1; j < m; ++ j) {for (int i = 0; i + (1 << j - 1) < n; ++ i) {st[i][j] = st[i][j - 1] + st[i + (1 << j - 1)][j - 1];}}}Info query(int l, int r) {if (l > r) {return {-inf, inf};}int lg = std::__lg(r - l + 1);return st[l][lg] + st[r - (1 << lg) + 1][lg];}
};struct Info {i64 max, min;
};Info operator + (const Info & a, const Info & b) {Info res{};res.max = std::max(a.max, b.max);res.min = std::min(a.min, b.min);return res;
}void solve() {int n;std::cin >> n;std::vector<int> a(n);std::vector<Info> info(n + 1);for (int i = 0; i < n; ++ i) {std::cin >> a[i];info[i + 1] = {a[i], a[i]};}ST<Info> st(info);std::vector<i64> sum(n + 1);for (int i = 1; i <= n; ++ i) {sum[i] = sum[i - 1] + a[i - 1];}std::vector<i64> ans(n);ans[0] = st.query(1, n).max - st.query(1, n).min;for (int i = 1; i <= n; ++ i) {for (int j = i + 1; j <= n; ++ j) {auto l = st.query(1, i - 1);auto r = st.query(j + 1, n);ans[j - i] = std::max(ans[j - i], std::max({sum[j] - sum[i - 1], l.max, r.max}) - std::min(l.min, r.min));}}for (int i = 1; i + 2 < n; ++ i) {ans[i] = std::max(ans[i], ans[i - 1]);}for (int i = 0; i < n; ++ i) {std::cout << ans[i] << " \n"[i == n - 1];}
}