VP Educational Codeforces Round 65 (Rated for Div. 2)
A. Telephone Number
题意:判断一个数字串删去若干字符到长度为11的时候能不能是8这个数字开头。
判断第一个8前面的数能不能都删去。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::string s;
std::cin >> s;
int m = n - 11;
int p = s.find('8');
if (p == s.npos || p > m) {
std::cout << "NO\n";
} else {
std::cout << "YES\n";
}
}
B. Lost Numbers
题意:交互题。一个由\(\{{4, 8, 15, 16, 23, 42\}}\)组成的排列,一开始是隐藏的。你最多询问4次。每次问\(a_i \times a_j\)的值。求这个排列。
问\(a_1\)和\(a_2, a_3, a_4, a_5\)的乘积。就可以找出\(15\)或者\(23\)中至少一个数的位置。然后可以得到\(a_1\)的值,继而得到其它四个数的值。最后一个数就是没出现的那个数。
点击查看代码
int ask(int i, int j) {
std::cout << "? " << i << " " << j << std::endl;
int res;
std::cin >> res;
return res;
}
void solve() {
std::set<int> s{4, 8, 15, 16, 23, 42};
int ans[6]{}, b[6];
for (int i = 1; i <= 4; ++ i) {
b[i] = ask(1, i + 1);
}
if (b[1] % 15 == 0 && b[2] % 15 == 0 && b[3] % 15 == 0 && b[4] % 15 == 0) {
ans[0] = 15;
} else if (b[1] % 23 == 0 && b[2] % 23 == 0 && b[3] % 23 == 0 && b[4] % 23 == 0) {
ans[0] = 23;
} else {
for (int i = 1; i <= 4; ++ i) {
if (b[i] % 15 == 0) {
ans[0] = b[i] / 15;
break;
}
if (b[i] % 23 == 0) {
ans[0] = b[i] / 23;
break;
}
}
}
for (int i = 1; i <= 4; ++ i) {
ans[i] = b[i] / ans[0];
}
for (int i = 0; i <= 4; ++ i) {
s.erase(ans[i]);
}
ans[5] = *s.begin();
std::cout << "! ";
for (int i = 0; i <= 5; ++ i) {
std::cout << ans[i] << " ";
}
std::cout << std::endl;
}
C. News Distribution
题意:求一个点所在连通块的点的个数。
板子题。
点击查看代码
struct DSU {
std::vector<int> fa, cnt;
DSU(int _n) {
init(_n);
}
void init(int _n) {
fa.assign(_n, 0);
cnt.assign(_n, 1);
std::iota(fa.begin(), fa.end(), 0);
}
int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
bool merge(int x, int y) {
x = find(x), y = find(y);
if (x == y) {
return false;
}
fa[y] = x;
cnt[x] += cnt[y];
return true;
}
bool same(int x, int y) {
return find(x) == find(y);
}
int size(int x) {
return cnt[find(x)];
}
};
void solve() {
int n, m;
std::cin >> n >> m;
DSU d(n);
for (int i = 0; i < m; ++ i) {
int k;
std::cin >> k;
std::vector<int> a(k);
for (int i = 0; i < k; ++ i) {
std::cin >> a[i];
-- a[i];
}
for (int i = 1; i < k; ++ i) {
d.merge(a[0], a[i]);
}
}
for (int i = 0; i < n; ++ i) {
std::cout << d.size(i) << " \n"[i == n - 1];
}
}
D. Bicolored RBS
题意:把一个括号序列分成两份。使得其中嵌套括号最多的最小。
贪心的做。维护两个括号序列,每次遇到左括号就放到小的那个括号里,遇到右括号就把大的那个减小。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::string s;
std::cin >> s;
std::string ans(n, '0');
int cnt1 = 0, cnt2 = 0;
for (int i = 0; i < n; ++ i) {
if (s[i] == '(') {
if (cnt1 <= cnt2) {
++ cnt1;
} else {
++ cnt2;
ans[i] = '1';
}
} else {
if (cnt1 >= cnt2) {
-- cnt1;
} else {
-- cnt2;
ans[i] = '1';
}
}
}
std::cout << ans << "\n";
}
E. Range Deleting
题意:一个数组\(a\),值域为\([1, x]\),求所有的\((l, r), 1 \leq l \leq r \leq x\)满足删去值在这个区间的数后数组非递减。
首先对于一个已经非递减的数组。不管这么删都是非递减的。那么这提示我们,如果\((l, r)\)合法,那么\((l, [r, x])\)都合法。
于是我们可以枚举\(l\),维护一个合法的\(r\)。
我们只需要维护一个前缀是否合法,和一个后缀是否合法,然后看能不能把这个前缀和后缀拼起来也合法。可以记录\(L_i, R_i\)表示\(i\)出现的最左边和最右边的位置,那么\(l\)放到\(r\)的前面需要满足\(\max_{i=1}^{l} R_i \leq \min_{i=r}^{x} L_i\)。
点击查看代码
void solve() {
int n, x;
std::cin >> n >> x;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
std::vector<int> l(x + 2, n), r(x + 2, -1);
for (int i = 0; i < n; ++ i) {
l[a[i]] = std::min(l[a[i]], i);
r[a[i]] = std::max(r[a[i]], i);
}
std::vector<int> st(x + 2), L(x + 2);
st[x + 1] = 1; L[x + 1] = n;
for (int i = x; i >= 1; -- i) {
if (r[i] < L[i + 1]) {
st[i] = 1;
} else {
break;
}
L[i] = std::min(L[i + 1], l[i]);
}
i64 ans = 0;
for (int i = 1, maxr = -1, j = 1; i <= x; ++ i) {
j = std::max(i, j);
while (j <= x && (!st[j + 1] || maxr > L[j + 1])) {
++ j;
}
if (j > x) {
break;
}
ans += x - j + 1;
if (l[i] < maxr) {
break;
}
maxr = std::max(maxr, r[i]);
}
std::cout << ans << "\n";
}
F. Scalar Queries
题意:长度为\(n\)的数组\(a\)。定义\(f(l, r)\)价值为\(b = sort(a[l, .., r])\),也就是将这一段子数组排序后,价值为\(\sum_{i=1}^{r-l+1} b_i \times i\)。求\(\sum_{l=1}^{n} \sum_{r=l}^{n} f(l, r)\)。
这种题,考虑单个点的贡献。也就是对于\(a_i\),它乘上的系数\(1, 2, ... n\)分别有多少个。发现并不好计算,考虑把大的贡献拆成小的贡献,设有\(k\)个区间使得\(a_i\)的排名小于等于\(j\),那么加上\(a_i \times k\)。也就是对于每个\(a_j < a_i, j < i\),我们加上\(j\times (n - i+ 1) \times a_i\)。发现对于\(a_i\)排名为\(1\)的区间,我们把其它排名大于等于\(1\)的区间也算上了,那么对于排名等于\(2\)的区间,本来要让\(a_i\)乘\(2\),但是我们在排名为\(1\)的区间多算了\(1\)的贡献,那么也只需要给每个区间算\(1\)的贡献。同理得对于排名为\(k\)的区间,因为\([1, k-1]\)都给这些区间算了一个贡献,那么这个区间只需要加上剩下\(1\)的贡献。
那么我们只需要把\(j < i\)和\(j > i\)的情况分别算一遍。例如,对于\(j < i,a_j < a_i\),贡献为\(j\times (n - i+1)\times a_i\),发现\((n-i+1)\times a_i\)与\(j\)无关,那么我们用树状数组记录前面每个\(a_j < a_i\)的\(j\)的和。\(j > i\)的情况同理。
代码省略取模类。
点击查看代码
template <class T>
struct Fenwick {
int n;
std::vector<T> tr;
Fenwick(int _n) {
init(_n);
}
void init(int _n) {
n = _n;
tr.assign(_n + 1, T{});
}
void add(int x, const T &v) {
for (int i = x; i <= n; i += i & -i) {
tr[i] = tr[i] + v;
}
}
T query(int x) {
T res{};
for (int i = x; i; i -= i & -i) {
res = res + tr[i];
}
return res;
}
T sum(int l, int r) {
return query(r) - query(l - 1);
}
};
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
auto b = a;
std::ranges::sort(b);
b.erase(std::unique(b.begin(), b.end()), b.end());
auto get = [&](int x) -> int {
return std::ranges::lower_bound(b, x) - b.begin() + 1;
};
int m = b.size();
Fenwick<Z> tr(m + 1);
Z ans = 0;
for (int i = 0; i < n; ++ i) {
int k = get(a[i]);
tr.add(k, i + 1);
ans += tr.query(k) * (n - i) * a[i];
}
tr.init(m + 1);
for (int i = n - 1; i >= 0; -- i) {
int k = get(a[i]);
ans += tr.query(k) * (i + 1) * a[i];
tr.add(k, n - i);
}
std::cout << ans << "\n";
}

浙公网安备 33010602011771号