Codeforces Round #832 (Div. 2)
A. Two Groups (CF 1747 A)
题目大意
给定一个整数数组\(a\),要求将 \(a\)分成两部分\(s1,s2\),要求两部分的和的绝对值的差最大。
即最大化\(|\sum(s_1)| - |\sum(s_2)|\)
解题思路
数组\(a\)有正有负,要最大化差值的话,全部正数肯定全部放到 \(s1\),然后考虑负数放哪里。
负数放\(s1\)的话,差值肯定会减小,而放到\(s2\)的话差值也会减小。而如果把一些正数放到\(s2\),值也一样会减小。
因此答案就是 \(\sum(a)\)
当然因为是绝对值,如果负数和\(>\)正数和,那就把负数放到 \(s1\),正数放到 \(s2\)。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t;
cin >> t;
while(t--){
int n;
cin >> n;
LL sum = 0;
for(int i = 0; i < n; ++ i){
LL x;
cin >> x;
sum += x;
}
cout << abs(sum) << '\n';
}
return 0;
}
B. BAN BAN (CF 1747 B)
题目大意
定义\(s(n)\)为字符串 \(BAN\)重复 \(n\)次连接起来的字符串。
要求最小化对\(s(n)\)的操作,使得\(s(n)\)的任意子序列都不是 \(BAN\)。
操作为,交换 \(s(n)\)的两个位置的字符。
解题思路
随便臆想一下,比如把第一个\(A\)和最后一个 \(N\)交换,第二个 \(A\)和倒数第二个 \(N\)交换,这样就OK了。
至于为什么是最小嘛(我也不知道,感觉每次操作都是拆散一个 \(BAN\)
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t;
cin >> t;
while(t--){
int n;
cin >> n;
vector<pair<int, int>> op;
for(int i = 0, j = n - 1; i <= j; ++ i, -- j){
int posA = i * 3 + 1;
int posN = j * 3 + 2;
op.push_back({posA + 1, posN + 1});
}
cout << op.size() << '\n';
for(auto &i : op){
cout << i.first << ' ' << i.second << '\n';
}
}
return 0;
}
C. Swap Game (CF 1747 C)
题目大意
给定一个正整数数组\(a\), \(Alice\)和 \(Bob\)玩一个游戏, \(Alice\)先手,先后进行如此操作:
- 如果 \(a[1]==0\),则输了
- 选择 \(i > 1\) ,交换\(a[1]\)和 \(a[i]\),并将\(a[i]\)减一。
解题思路
关注数组\(a\)的最小值的位置。如果 \(a[1]\)是最小值,那么对于 \(Alice\)的操作,它都不能把最小值放在\(a[1]\)。 (\(Alice\)选择 \(i\),那么 \(Bob\)选择 \(i\), 那么最小值又回到 \(a[1]\)。此时\(Bob\)必胜。
而如果 \(a[1]\)不是最小值,那么\(Alice\)选择最小值那个下标,同样的道理 \(Bob\)也无法把最小值放在 \(a[1]\)。此时 \(Alice\)必胜。
因此如果\(a[1]\)是最小值,则 \(Bob\)必胜,否则是 \(Alice\)必胜。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t;
cin >> t;
while(t--){
int n;
cin >> n;
vector<int> a(n);
for(auto &i : a)
cin >> i;
if (a[0] <= *min_element(a.begin() + 1, a.end())){
cout << "Bob" << '\n';
}else{
cout << "Alice" << '\n';
}
}
return 0;
}
D. Yet Another Problem (CF 1747 D)
题目大意
给定一个自然数数组\(a\),回答 \(q\)组询问。
每组询问包含 \(L,R\),对 \(a[L..R]\)数组操作,每次操作 选择一个子区间,要求:
- 该区间长度为奇数
- 将该区间的每个数赋值为该区间的值的异或
要求最小化操作次数,使得\(a[L..R]\)变成 \(0\)。
如果不能则输出 \(-1\)。
解题思路
我们考虑一个区间的二进制下的某一位,如果其异或下值为\(1\),注意到区间长度是奇数,那么执行操作后,该区间的值还是不变。即操作不改变区间的异或值。
因此如果\(a[L..R]\)本身异或值不为 \(0\),则不可行。
如果为\(0\),且\(a[L..R]\)长度恰好为奇数,则直接选择该区间,操作一次即可。
如果为偶数,且恰好第一个数或最后一个数为 \(0\),则也可以选择长度为奇数的区间,操作一次即可。
否则,得操作两次,找到一个 \(a[L..M]\),使得其异或值为 \(0\), 此时\(a[M+1..R]\)异或值也为 \(0\) (因为\(a[L..R]\)异或值为 \(0\))。如果找不到则不可行。找到的话操作两次即可。
注意要保证区间长度为奇数。找的话可以下标按奇偶性分类后,二分符合条件的下标。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, q;
cin >> n >> q;
vector<int> a(n + 1);
vector<int> zero(n + 1);
vector<int> xx(n + 1);
unordered_map<int, vector<int>> pos[2];
for(int i = 1; i <= n; ++ i){
cin >> a[i];
zero[i] = zero[i - 1] + (a[i] == 0);
xx[i] = xx[i - 1] ^ a[i];
pos[i & 1][xx[i]].push_back(i);
}
while(q--){
int l, r;
cin >> l >> r;
if ((xx[r] ^ xx[l - 1]) != 0){
cout << "-1" << '\n';
continue;
}
if (zero[r] == zero[l - 1] + r - l + 1){
cout << "0" << '\n';
continue;
}
if ((r - l + 1) & 1){
cout << "1" << '\n';
continue;
}
if (a[l] == 0 || a[r] == 0){
cout << "1" << '\n';
continue;
}
int ans = 0;
while(l <= r){
int xsum = xx[l - 1];
auto& target = pos[l & 1][xsum];
auto smallR = upper_bound(target.begin(), target.end(), r);
if (smallR == target.begin() || *(--smallR) < l){
ans = -1;
break;
}
int nxt = *smallR;
debug(l, nxt, zero[l - 1], zero[nxt]);
ans += (zero[nxt] != zero[l - 1] + nxt - l + 1);
l = nxt + 1;
}
cout << ans << '\n';
}
return 0;
}
E. List Generation (CF 1747 E)
题目大意
给定\(n,m\),构造两个数组 \((a,b)\),满足:
- \(len(a) = len(b) = k\)
- \(a_0 = 0, b_0 = 0, a_k = n, b_k = m\)
- \(a,b\)数组非递减
问能构造出来的 \((a,b)\)的所有 \(k\)的和。
解题思路
注意到\(a,b\)数组非递减,对其求差分数组。
枚举差分数组长度 \(k\), 则\(a,b\)数组长度是 \(k+1\)。
然后问题就转换成:
- 将 \(n\)分配到 \(a\)的差分数组\(ca\)里
- 将 \(m\)分配到 \(b\)的差分数组\(cb\)里
- 不存在下标\(i\),使得\(ca_i = cb_i = 0\)
求满足上述要求的方案数。
先把式子列出来。
枚举\(ca\)中非 \(0\)的位置,分配数使其和为 \(n\)。这个为经典的正整数方程解个数问题。
知道 \(ca\)为 \(0\)的位置,对于 \(cb\)来说,\(ca\)为\(0\)的 位置的\(cb\)必须是正数,不为 \(0\)的则可以是自然数。其和为 \(m\)。这也是整数方程解个数问题。
因此式子为:
该表达式计算复杂度为\(O(n^2)\),得优化一下第二个求和。
第二个求和的三个组合数都跟\(l\)有关,我们的目标是尽量化成只有一个跟 \(l\)有关,但目测很难,尝试一下交换求和顺序。
第二个求和还有两个组合数,观察其形式,可以用以下恒等式替换
由组合数恒等式可以得到
因此
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
#define FOR(i, x, y) for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i)
#define FORD(i, x, y) for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i)
const int N = 1e7 + 8;
const int mo = 1e9 + 7;
int bin(int x, int n, int MOD) {
int ret = MOD != 1;
for (x %= MOD; n; n >>= 1, x = 1ll * x * x % MOD)
if (n & 1) ret = 1ll * ret * x % MOD;
return ret;
}
inline int get_inv(LL x, LL p) { return bin(x, p - 2, p); }
vector<int> invf, fac;
void fac_inv_init(int n, int p) {
FOR (i, 1, n)
fac[i] = 1ll * i * fac[i - 1] % p;
invf[n - 1] = bin(fac[n - 1], p - 2, p);
FORD (i, n - 2, -1)
invf[i] = 1ll * invf[i + 1] * (i + 1) % p;
}
int C(int n, int m){
if (n < m)
return 0;
return 1ll * fac[n] * invf[m] % mo * invf[n - m] % mo;
}
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
fac.resize(N);
fac[0] = 1;
invf.resize(N);
fac_inv_init(N, mo);
int inv2 = get_inv(2, mo);
int t;
cin >> t;
while(t--){
int n, m;
cin >> n >> m;
int ans = 0;
int tmp = 0;
if (m == 1)
tmp = inv2;
else
tmp = bin(2, m - 2, mo);
int zu1 = 1ll * tmp * 4 % mo;
int zu2 = 1ll * m * tmp % mo * 2 % mo;
int zu3 = 1ll * m * (m + 1) % mo * tmp % mo;
for(int l = 1; l <= n; ++ l){
int tmp1 = 1ll * C(n - 1, l - 1) * C(m + l - 1, l) % mo;
int tmp2 = (0ll + zu3 + (2ll * l + 1) * zu2 % mo + 1ll * (l + 1) * l % mo * zu1 % mo) % mo;
ans = (0ll + ans + 1ll * tmp1 * tmp2 % mo) % mo;
}
ans = 1ll * ans * get_inv(m, mo) % mo;
cout << ans << '\n';
}
return 0;
}
还有另一种解法。注意到是非递减的,考虑在一个二维网格R,左下角\((0,0)\),右上角\((n,m)\),只能向右走和向上走,其路径上有一些关键点(对应的就是一个 \((a_i, b_i)\))。假设路径上有 \(k\)个关键点,有 \(cnt_k\)种 \(k\)个关键点的合法摆放(即有一条路径穿过这 \(k\)个关键点),答案就是 \(\sum_{k=0}^{\infty}k \times cnt_k\)
关键是如何求出\(cnt_k\)。
观察一条路径,会发现有一些关键点是路径的拐点(向右后向上,或者向上后向右)。我们枚举其中一种拐点(比如向右后向上)数量\(i\),那再枚举 \(j\)个关键点在剩下的\(n+m-1-i\)点上 ,因此
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
#define FOR(i, x, y) for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i)
#define FORD(i, x, y) for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i)
const int N = 1e7 + 8;
const int mo = 1e9 + 7;
int bin(int x, int n, int MOD) {
int ret = MOD != 1;
for (x %= MOD; n; n >>= 1, x = 1ll * x * x % MOD)
if (n & 1) ret = 1ll * ret * x % MOD;
return ret;
}
inline int get_inv(LL x, LL p) { return bin(x, p - 2, p); }
vector<int> invf, fac;
void fac_inv_init(int n, int p) {
FOR (i, 1, n)
fac[i] = 1ll * i * fac[i - 1] % p;
invf[n - 1] = bin(fac[n - 1], p - 2, p);
FORD (i, n - 2, -1)
invf[i] = 1ll * invf[i + 1] * (i + 1) % p;
}
int C(int n, int m){
if (n < m)
return 0;
return 1ll * fac[n] * invf[m] % mo * invf[n - m] % mo;
}
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
fac.resize(N);
fac[0] = 1;
invf.resize(N);
fac_inv_init(N, mo);
int inv2 = get_inv(2, mo);
int t;
cin >> t;
while(t--){
int n, m;
cin >> n >> m;
int ans = 0;
if (n > m)
swap(n, m);
int tmp = bin(2, n + m - 1, mo);
for(int l = 0; l <= n; ++ l){
int t1 = C(n, l);
int t2 = C(m, l);
ans = (0ll + ans + 1ll * t1 * t2 % mo * ((l + 2ll) * (tmp * 2ll) % mo + (n + m - 1ll - l) * tmp % mo) % mo) % mo;
tmp = 1ll * tmp * inv2 % mo;
}
ans = 1ll * ans * inv2 % mo;
cout << ans << '\n';
}
return 0;
}