补题链接:https://ac.nowcoder.com/acm/contest/72647#question
作为复赛,本场比赛难度略高于初赛,且都是原创题,总体比赛强度还是比初赛高一个档次的。复赛第一场一共8题,其中A组使用6题:BDEFGH;B组使用6题:ACDEFG
A 小红劈字符串
比赛通过率:87.79%
知识点:字符串,模拟
思路:签到题。只需要判断字符串长度是否是3的倍数即可。如果是3的倍数,则在的地方添加一个空格;否则无解。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int a[101026],sum[101010];
signed main(){
int n,m,i,j,k,x,y,c=0;
string s;
cin>>s;
if(s.length()%3!=0)return cout<<-1,0;
for(i=0;i<s.length();i++){
if(i*3==s.length()*2)cout<<" ";
cout<<s[i];
}
}
B 赝品
比赛通过率:82.33%
知识点:数组、哈希、模拟
思路:建议使用map等哈希容器(java为HashMap,python为dict),这样可以比较方便统计每个元素的次数。另外也可以通过排序等方式来达成目的。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int a[101026],sum[101010];
signed main(){
int n,i,j,k,x,y,c=0;
cin>>n;
map<int,int>m;
for(i=0;i<n;i++)cin>>x,m[x]++;
vector<int>v;
for(auto i:m){
if(i.second==1)v.push_back(i.first);
}
cout<<v.size()<<'\n';
for(auto i:v)cout<<i<<" ";
}
C 小红的数字分裂
比赛通过率:36.61%
知识点:数论、贪心
思路:一个比较显然的结论是,最终需要将数组的元素变成所有元素的gcd(最大公约数)。因此每个元素需要分裂的次数可以直接求出来。这个结论的证明可以结合辗转相除法的性质,请大家自行思考。
参考代码:
#include <bits/stdc++.h>
using namespace std;
int a[101010];
int main() {
int n,i,g=0;
cin>>n;
for(i=0;i<n;i++){
cin>>a[i];
g=__gcd(a[i],g);
}
long long s=0;
for(i=0;i<n;i++){
s+=a[i]/g-1;
}
cout<<s;
}
D 小红的字符串同构
比赛通过率:26.21%
知识点:组合数学
思路:我们可以先计算出满足第一个条件的字符串数量:用乘法原理计算,答案为。
然后我们需要减去和原字符串同构的情况,答案为最大的字符减去最小的字符这么多。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int a[101026],sum[101010];
int suma[101010],sumb[101010];
int pow2[101010];
int mod=1e9+7;
signed main(){
int n,i,j=0,k,x,y,c=0;
string s;
cin>>s;
int ma=0,mi=1e9;
int res=1;
for(auto i:s){
ma=max(ma,(int)(i-'a'));
mi=min(mi,(int)(i-'a'));
res=res*25%mod;
}
cout<<res-(26-(ma-mi+1));
}
E 小红的树上赋值方案
比赛通过率:4.53%
知识点:树形dp
思路:我们记录dp[u][i][j]代表以u节点的子树,当u节点赋值为的时候,该子树权值和模3等于的方案数。在dfs的时候转移即可。
参考代码:
#include <bits/stdc++.h>
using ll = long long;
template <typename T, auto f = std::multiplies<T>()>
constexpr static T power(T a, int64_t b) {
assert(b >= 0);
T res;
if constexpr (std::is_arithmetic_v<T>) {
res = 1;
} else {
res = a.identity();
}
while (b) {
if (b & 1) {
res = f(res, a);
}
b >>= 1;
a = f(a, a);
}
return res;
}
template <typename T, T MOD>
struct ModInt {
using prod_type = std::conditional_t<std::is_same_v<T, int>, int64_t, __int128>;
T val;
constexpr ModInt(const int64_t v = 0) : val(v % MOD) { if (val < 0) val += MOD; }
constexpr ModInt operator+() const { return ModInt(val); }
constexpr ModInt operator-() const { return ModInt(MOD - val); }
constexpr ModInt inv() const { return power(*this, MOD - 2); }
constexpr friend ModInt operator+(ModInt lhs, const ModInt rhs) { return lhs += rhs; }
constexpr friend ModInt operator-(ModInt lhs, const ModInt rhs) { return lhs -= rhs; }
constexpr friend ModInt operator*(ModInt lhs, const ModInt rhs) { return lhs *= rhs; }
constexpr friend ModInt operator/(ModInt lhs, const ModInt rhs) { return lhs /= rhs; }
constexpr ModInt &operator+=(const ModInt x) {
if ((val += x.val) >= MOD) val -= MOD;
return *this;
}
constexpr ModInt &operator-=(const ModInt x) {
if ((val -= x.val) < 0) val += MOD;
return *this;
}
constexpr ModInt &operator*=(const ModInt x) {
val = prod_type(val) * x.val % MOD;
return *this;
}
constexpr ModInt &operator/=(const ModInt x) { return *this *= x.inv(); }
bool operator==(const ModInt b) const { return val == b.val; }
bool operator!=(const ModInt b) const { return val != b.val; }
friend std::istream &operator>>(std::istream &is, ModInt &x) noexcept { return is >> x.val; }
friend std::ostream &operator<<(std::ostream &os, const ModInt x) noexcept { return os << x.val; }
constexpr static ModInt identity() { return 1; }
constexpr ModInt pow(int64_t p) { return power(*this, p); }
};
using Z = ModInt<int, 1'000'000'007>;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::string s;
std::cin >> s;
std::vector<std::vector<int>> g(n);
for (int i = 1; i < n; i++) {
int f;
std::cin >> f;
f--;
g[f].push_back(i);
}
std::vector dp(n, std::vector(3, std::vector<Z>(3)));
auto dfs = [&](auto &&self, int u) -> void {
dp[u][1][1] = 1;
dp[u][2][2] = 1;
for (auto v : g[u]) {
self(self, v);
for (int i = 1; i <= 2; i++) {
std::vector<Z> tmp(3);
for (int j = 0; j < 3; j++) {
for (int k = 0; k < 3; k++) {
tmp[(j + k) % 3] += dp[u][i][j] * (dp[v][1][k] + dp[v][2][k]);
}
}
dp[u][i] = tmp;
}
}
if (s[u] == 'R') {
for (int i = 1; i <= 2; i++) {
dp[u][i][1] = 0;
dp[u][i][2] = 0;
}
}
};
dfs(dfs, 0);
auto get = [&](int u) -> Z {
Z res = 0;
for (int i = 1; i <= 2; i++) {
for (int j = 0; j < 3; j++) {
res += dp[u][i][j];
}
}
return res;
};
std::cout << get(0) << "\n";
return 0;
}
F 小红的区间查询
比赛通过率:5.03%
知识点:线段树、离散化
思路:直接维护区间的相交情况比较困难。我们可以首先进行离散化,然后在值域上开线段树进行维护区间和。当添加一个区间时,我们进行区间加1;删除一个区间时,我们进行区间减1。这样,问题可以转化为,如果存在一个大于1的值,则意味着存在两个区间相交。这样我们可以通过询问全局最大值解决。
参考代码:
#include <bits/stdc++.h>
using ll = long long;
template<class Info, class Tag, class Merge = std::plus<Info>>
class LazySegmentTree {
private:
int n;
const Merge merge{};
std::vector<Info> info; // data of segment tree, 1-index
std::vector<Tag> tag; // lazy tag of segment tree
/* [x, y) and val: Add val to each element in range of [x, y)
* p: The id of subtree, which is an index of vector 'info'.
* [l, r): The range of p.
*/
void innerPull(int p) {
info[p] = merge(info[p << 1], info[p << 1 | 1]);
}
void innerApply(int p, const Tag &v, int l, int r) {
// TODO: update the apply function
auto applyInfo = [&](Info &a, const Tag &b, int l, int r) {
a.min += b;
a.max += b;
};
auto applyTag = [&](Tag &a, const Tag &b, int l, int r) {
a += b;
};
applyInfo(info[p], v, l, r);
applyTag(tag[p], v, l, r);
}
void push(int p, int l, int r) {
if (tag[p] != Tag()) {
int m = (l + r) / 2;
innerApply(p << 1, tag[p], l, m);
innerApply(p << 1 | 1, tag[p], m, r);
tag[p] = Tag();
}
}
void update_(int p, int x, int y, const Tag &v, int l, int r) {
if (x <= l && r <= y) {
innerApply(p, v, l, r);
return;
}
int m = (l + r) / 2;
push(p, l, r);
if (x < m) update_(p << 1, x, y, v, l, m);
if (y > m) update_(p << 1 | 1, x, y, v, m, r);
innerPull(p);
}
/* Query the sum-up value of range [x, y). */
Info query_(int p, int x, int y, int l, int r) {
if (x <= l && r <= y) return info[p];
if (x >= r || y <= l) return Info();
int m = (l + r) / 2;
push(p, l, r);
return merge(query_(p << 1, x, y, l, m), query_(p << 1 | 1, x, y, m, r));
}
public:
LazySegmentTree(int n = 0) { init(n); }
LazySegmentTree(std::vector<Info> &init) : LazySegmentTree(init.size()) {
std::function<void(int, int, int)> build_ = [&](int p, int l, int r) {
if (r - l == 1) {
info[p] = init[l];
return;
}
int m = (l + r) / 2;
build_(p << 1, l, m);
build_(p << 1 | 1, m, r);
innerPull(p);
};
build_(1, 0, n);
}
void init(int n) {
this->n = n;
info.assign(4 << std::__lg(n), Info(0, 0));
// info.resize(4 << std::__lg(n));
tag.resize(4 << std::__lg(n));
}
/* Add val to each element in range of [x, y) */
void update(int x, int y, Tag v) {
update_(1, x, y, v, 0, n);
}
/* Query the sum-up value of range [x, y) */
Info query(int x, int y) {
return query_(1, x, y, 0, n);
}
};
struct MinMax {
static constexpr int inf = 1e9;
int min, max;
MinMax(int min = inf, int max = -inf) : min(min), max(max) {}
MinMax operator+(const MinMax &rhs) const {
return MinMax(std::min(min, rhs.min), std::max(max, rhs.max));
}
MinMax &operator+=(const MinMax &rhs) {
return *this = *this + rhs;
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int m;
std::cin >> m;
std::set<std::pair<int, int>> st;
std::vector<std::tuple<char, int, int>> ops;
while (m--) {
char op;
int l, r;
std::cin >> op >> l >> r;
ops.emplace_back(op, l, r);
if (op == '+') {
assert(st.find(std::pair(l, r)) == st.end());
st.emplace(l, r);
} else {
assert(st.find(std::pair(l, r)) != st.end());
st.erase(std::pair(l, r));
}
}
std::vector<int> a;
for (auto [op, l, r] : ops) {
a.push_back(l);
a.push_back(r);
}
std::sort(a.begin(), a.end());
a.erase(std::unique(a.begin(), a.end()), a.end());
for (auto &[op, l, r] : ops) {
l = std::lower_bound(a.begin(), a.end(), l) - a.begin();
r = std::lower_bound(a.begin(), a.end(), r) - a.begin();
}
LazySegmentTree<MinMax, int> seg(a.size());
for (auto [op, l, r] : ops) {
if (op == '+') {
seg.update(l, r, 1);
} else {
seg.update(l, r, -1);
}
if (auto [min, max] = seg.query(0, a.size()); min < 0 || max > 1) {
std::cout << "Yes\n";
} else {
std::cout << "No\n";
}
}
return 0;
}
G 小红和小紫的取数游戏
比赛通过率:1.26%
知识点:博弈,数论,哈希
思路:首先我们将该博弈问题进行抽象,可以比较轻松得出以下结论:如果初始所有元素乘积为完全平方数,那么小紫获胜;否则小红获胜。
因此我们可以先用的根号分解,对于次数和为奇数的素因子,我们需要使得该素因子的次数变成偶数即可。那么这道题等价为:给定一个数组,询问有多少个连续子数组满足子数组所有元素中,出现次数为奇数素因子的乘积恰好等于。(为初始给定的数的奇数次数因子乘积)。
要解决这个问题,我们可以首先使用筛法,预处理出每个元素的素因子次数。然后使用哈希批量维护区间信息。在处理过程中,我们可以对初始的因子进行特殊标记,减少哈希中需要处理的不同因子数论,避免出现tle或者mle。
参考代码:
#include <bits/stdc++.h>
using ll = long long;
struct EulerSieveSimple {
const int N;
std::vector<int> minp, primes;
std::map<int, int> prime_id;
EulerSieveSimple(int n) : N(n), minp(n + 1) {
for (int i = 2; i <= N; ++i) {
if (!minp[i]) {
minp[i] = i;
prime_id[i] = primes.size();
primes.push_back(i);
}
for (auto p : primes) {
if (i * p > n) break;
minp[i * p] = p;
if (i % p == 0) break;
}
}
}
std::vector<std::pair<int, int>> prime_factors(int n) {
std::vector<std::pair<int, int>> factors;
while (n > 1) {
int p = minp[n], cnt = 0;
while (n % p == 0) {
cnt++;
n /= p;
}
factors.emplace_back(p, cnt);
}
return factors;
}
} sieve(1'000'000);
template <typename T, auto f = std::multiplies<T>()>
constexpr static T power(T a, int64_t b) {
assert(b >= 0);
T res;
if constexpr (std::is_arithmetic_v<T>) {
res = 1;
} else {
res = a.identity();
}
while (b) {
if (b & 1) {
res = f(res, a);
}
b >>= 1;
a = f(a, a);
}
return res;
}
template <typename T, T MOD>
struct ModInt {
using prod_type = std::conditional_t<std::is_same_v<T, int>, int64_t, __int128>;
T val;
constexpr ModInt(const int64_t v = 0) : val(v % MOD) { if (val < 0) val += MOD; }
constexpr ModInt operator+() const { return ModInt(val); }
constexpr ModInt operator-() const { return ModInt(MOD - val); }
constexpr ModInt inv() const { return power(*this, MOD - 2); }
constexpr friend ModInt operator+(ModInt lhs, const ModInt rhs) { return lhs += rhs; }
constexpr friend ModInt operator-(ModInt lhs, const ModInt rhs) { return lhs -= rhs; }
constexpr friend ModInt operator*(ModInt lhs, const ModInt rhs) { return lhs *= rhs; }
constexpr friend ModInt operator/(ModInt lhs, const ModInt rhs) { return lhs /= rhs; }
constexpr ModInt &operator+=(const ModInt x) {
if ((val += x.val) >= MOD) val -= MOD;
return *this;
}
constexpr ModInt &operator-=(const ModInt x) {
if ((val -= x.val) < 0) val += MOD;
return *this;
}
constexpr ModInt &operator*=(const ModInt x) {
val = prod_type(val) * x.val % MOD;
return *this;
}
constexpr ModInt &operator/=(const ModInt x) { return *this *= x.inv(); }
bool operator==(const ModInt b) const { return val == b.val; }
bool operator!=(const ModInt b) const { return val != b.val; }
friend std::istream &operator>>(std::istream &is, ModInt &x) noexcept { return is >> x.val; }
friend std::ostream &operator<<(std::ostream &os, const ModInt x) noexcept { return os << x.val; }
constexpr static ModInt identity() { return 1; }
constexpr ModInt pow(int64_t p) { return power(*this, p); }
};
namespace Hashing {
constexpr uint64_t mod = 1'000'000'000'000'000'003ULL;
using hash_t = ModInt<int64_t, mod>;
const hash_t base = std::mt19937_64(std::chrono::steady_clock::now().time_since_epoch().count())() % mod;
static std::vector<hash_t> pow{1};
static void expand_pow(size_t sz) {
if (pow.size() - 1 >= sz) {
return;
}
auto old_size = pow.size();
pow.resize(sz * 2 + 1);
for (auto i = old_size; i <= sz * 2; i++) {
pow[i] = pow[i - 1] * base;
}
}
static void flip(uint64_t &h, int p) {
if (p >= pow.size()) {
expand_pow(p);
}
h ^= pow[p].val;
}
} // namespace Hashing
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
ll n, x;
std::cin >> n >> x;
std::vector<ll> a(n);
for (auto &i : a) {
std::cin >> i;
}
uint64_t hash_x = 0;
for (auto p : sieve.primes) {
int c = 0;
while (x % p == 0) {
x /= p;
c++;
}
if (c % 2 == 1) {
Hashing::flip(hash_x, sieve.prime_id[p]);
}
}
if (x != 1) {
std::cout << "0\n";
return 0;
}
std::vector<uint64_t> hash_a;
for (auto &i : a) {
uint64_t hash = 0;
for (auto [p, c] : sieve.prime_factors(i)) {
if (c % 2 == 1) {
Hashing::flip(hash, sieve.prime_id[p]);
}
}
hash_a.push_back(hash);
}
std::map<uint64_t, int> cnt{std::pair(0ULL, 1)};
ll ans = 0;
uint64_t cur = 0;
for (int i = 0; i < n; i++) {
cur ^= hash_a[i];
if (cnt.count(cur ^ hash_x)) {
ans += cnt[cur ^ hash_x];
}
cnt[cur]++;
}
std::cout << ans << "\n";
return 0;
}
H 小红的红色段数量
比赛通过率:5.48%
知识点:启发式合并
思路:本题其实只需要求出每个节点染红后,最终排列中“红色段”数量是多少即可。该方案可以用树上启发式合并来解决:首先使用set维护子树节点信息,当添加/删除节点时,需要同时在set中查询红色段的数量影响。随后将小的子树的set合并到大的上面即可。
参考代码:
#include <bits/stdc++.h>
using ll = long long;
struct HLD {
int n;
std::vector<int> a, pos;
std::vector<int> sz, fa;
std::vector<std::vector<int>> g;
std::vector<bool> exist;
std::vector<int> ans;
int res;
HLD() {}
HLD(int n) {
init(n);
}
void init(int n) {
this->n = n;
a.resize(n);
pos.resize(n);
sz.resize(n);
fa.resize(n);
g.assign(n, {});
exist.assign(n, false);
ans.resize(n);
res = 0;
}
void addEdge(int u, int v) {
g[u].push_back(v);
g[v].push_back(u);
}
void work(int root = 0) {
fa[root] = -1;
dfs1(root);
for (int i = 0; i < n; i++) {
pos[a[i]] = i;
}
solve(root, true);
}
void dfs1(int u) {
if (fa[u] != -1) {
g[u].erase(std::find(g[u].begin(), g[u].end(), fa[u]));
}
sz[u] = 1;
for (auto &v : g[u]) {
fa[v] = u;
dfs1(v);
sz[u] += sz[v];
if (sz[v] > sz[g[u][0]]) {
std::swap(v, g[u][0]);
}
}
}
void add_subtree(int u, int ignore = -1) {
exist[pos[u]] = true;
res++;
if (pos[u] - 1 >= 0 && exist[pos[u] - 1]) {
res--;
}
if (pos[u] + 1 < n && exist[pos[u] + 1]) {
res--;
}
for (auto v : g[u]) {
if (v == ignore) continue;
add_subtree(v);
}
}
void delete_subtree(int u) {
exist[pos[u]] = false;
res--;
if (pos[u] - 1 >= 0 && exist[pos[u] - 1]) {
res++;
}
if (pos[u] + 1 < n && exist[pos[u] + 1]) {
res++;
}
for (auto v : g[u]) {
delete_subtree(v);
}
}
void solve(int u, bool keep) {
for (auto v : g[u]) {
if (v == g[u].front()) continue;
solve(v, false);
}
if (g[u].size()) {
solve(g[u].front(), true);
}
add_subtree(u, g[u].size() ? g[u].front() : -1);
ans[u] = res;
if (!keep) {
delete_subtree(u);
}
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
HLD hld(n);
for (auto &i : hld.a) {
std::cin >> i;
i--;
}
for (int i = 0; i < n - 1; i++) {
int u, v;
std::cin >> u >> v;
u--, v--;
hld.addEdge(u, v);
}
hld.work();
std::cout << std::fixed << std::setprecision(10);
std::cout << std::accumulate(hld.ans.begin(), hld.ans.end(), 0.) / n << '\n';
return 0;
}
全部评论
(18) 回帖