【题解】2023年第六届传智杯复赛第二场题解
补题连接:https://ac.nowcoder.com/acm/contest/72974
排行榜(此为清理作弊之前的榜):
A组:https://acexam.nowcoder.com/exam-rank?uid=1&paperId=17312728
B组:https://acexam.nowcoder.com/exam-rank?uid=1&paperId=17312729
复赛第二场的难度综合来看和第一场接近。略微降低了后期题难度,中期题增加了一个偏模拟的解析几何题供算法基础较差的同学冲刺,可惜通过率并不高。
一共8题,其中A组使用6题:ACDEGH;B组使用6题:ABDEFG
A 小红称体重
通过率:94.86%
知识点:模拟
纯签到题,按题意模拟遍历数组即可。
参考代码:
#include <bits/stdc++.h>
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, x;
std::cin >> n >> x;
std::vector<int> a(n);
for (auto &i : a) {
std::cin >> i;
}
int ans = 0;
for (int i = 1; i < n; i++) {
if (a[i - 1] < x && a[i] >= x) {
ans++;
}
}
std::cout << ans << "\n";
return 0;
}
B 小红的回文子串
通过率:52.69%
知识点:字符串,贪心、枚举
本题最优复杂度的做法是O(n),即每次修改字符时要么和
相同,要么和
相同。枚举时维护对答案贡献的增减即可。
不过本题的数据范围仅有100,可以直接枚举每种修改方式的结果计算答案。
参考代码:
#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;
n=s.length();
for(i=2;i<n;i++)c+=s[i]==s[i-2];
for(i=2;i<n-2;i++){
if(s[i-2]==s[i+2]&&s[i-2]!=s[i])return cout<<c+2,0;
}
for(i=2;i<n;i++){
if(s[i-2]!=s[i]&&(i>=n-2||s[i]!=s[i+2]))return cout<<c+1,0;
if(s[i-2]!=s[i]&&(i<=3||s[i-2]!=s[i-4]))return cout<<c+1,0;
}
cout<<c;
}
C 小红的字母游戏
通过率:65.05%
知识点:贪心,排序
一个显然的贪心思路是,优先选择在中出现次数更多的字母。因此我们可以先统计每个字母的出现次数,然后降序遍历计算即可。
#include <bits/stdc++.h>
using ll = long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, k;
std::cin >> n >> k;
std::string s;
std::cin >> s;
std::vector<int> cnt(26);
for (auto c : s) {
cnt[c - 'a']++;
}
std::sort(cnt.begin(), cnt.end(), std::greater());
ll ans = 0;
for (int i = 0; i < 26; i++) {
if (k > cnt[i]) {
ans += 1LL * cnt[i] * cnt[i];
k -= cnt[i];
} else {
ans += 1LL * k * k;
break;
}
}
std::cout << ans << '\n';
return 0;
}
D 小红的质数数组
通过率:33.90%
知识点:数论,模拟
我们可以先用筛法将不超过2e5范围内的素数全部筛出来,这样后面就可以O(1)判断某个数是不是素数了。现在需要找到一个下标满足:交换
后,相邻两数之和都是素数。一个比较直接的方法是模拟每次交换,看看新增的“相邻两数之和”和减少的“相邻两数之和”对答案的影响即可。
参考代码(模拟交换):
#include<bits/stdc++.h>
using namespace std;
int isp[202020],a[101010];
int main(){
int n,i,j,c=0;
for(i=1;i<=2e5;i++)isp[i]=1;
isp[1]=0;
for(i=1;i<=2e5;i++){
if(!isp[i])continue;
for(j=i*2;j<=2e5;j+=i)isp[j]=0;
}
cin>>n;
vector<int>v;
for(i=1;i<=n;i++)cin>>a[i];
for(i=2;i<=n;i++)c+=isp[a[i]+a[i-1]];
for(i=1;i<n;i++){
int cc=c;
if(i>1)cc-=isp[a[i]+a[i-1]];
if(i<n-1)cc-=isp[a[i+1]+a[i+2]];
if(i>1)cc+=isp[a[i+1]+a[i-1]];
if(i<n-1)cc+=isp[a[i]+a[i+2]];
if(cc==n-1)v.push_back(i);
}
if(v.size()==1)cout<<v[0]<<'\n';
else cout<<-1<<'\n';
}
参考代码2(树状数组维护前缀和):
#include <bits/stdc++.h>
using ll = long long;
template <typename T>
struct Fenwick {
int n;
std::vector<T> a;
Fenwick(int n = 0) {
init(n);
}
void init(int n) {
this->n = n;
a.assign(n, T());
}
void add(int x, T v) {
for (int i = x + 1; i <= n; i += i & -i) {
a[i - 1] += v;
}
}
// return the sum of [0, x)
T sum(int x) {
auto ans = T();
for (int i = x; i > 0; i -= i & -i) {
ans += a[i - 1];
}
return ans;
}
// return the sum of [l, r)
T rangeSum(int l, int r) {
return sum(r) - sum(l);
}
int kth(T k) {
int x = 0;
for (int i = 1 << std::__lg(n); i; i /= 2) {
if (x + i <= n && k >= a[x + i - 1]) {
x += i;
k -= a[x - 1];
}
}
return x;
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<int> a(n);
for (auto &i : a) {
std::cin >> i;
}
Fenwick<int> fen(n - 1);
auto is_prime = [&](int x) {
for (int i = 2; i * i <= x; ++i) {
if (x % i == 0) {
return false;
}
}
return true;
};
for (int i = 0; i < n - 1; i++) {
if (is_prime(a[i] + a[i + 1])) {
fen.add(i, 1);
}
}
std::vector<int> ans;
for (int i = 0; i < n - 1; i++) {
std::swap(a[i], a[i + 1]);
int sum = is_prime(a[i] + a[i + 1]);
if (i - 1 >= 0) {
sum += is_prime(a[i - 1] + a[i]);
sum += fen.rangeSum(0, i - 1);
}
if (i + 2 < n) {
sum += is_prime(a[i + 1] + a[i + 2]);
sum += fen.rangeSum(i + 2, n - 1);
}
if (sum == n - 1) {
ans.push_back(i);
}
std::swap(a[i], a[i + 1]);
}
if (ans.size() == 1) {
std::cout << ans[0] + 1 << "\n";
} else {
std::cout << "-1\n";
}
return 0;
}
E 小红的平面划线
通过率:1.17%
知识点:几何,枚举
考虑到一共最多只有180个点,因此直接枚举即可:首先
枚举两点确定直线,然后
进行判定。
判定点在直线上/左侧/右侧需要使用叉乘,根据叉乘结果的正负性得知答案。
本题还有个坑点,即的时候(此时一共3个点),此时可以随机在平面上任取一点(比如(114514,1919810)),枚举这个点和三个点中任意一点进行判定即可。
参考代码:
#include <iostream>
#include <vector>
using namespace std;
#define point pair<int,int>
pair<int,int>a[202];
int jud(point a,point b,point c){
point ab={b.first-a.first,b.second-a.second};
point bc={c.first-b.first,c.second-b.second};
int cnt=ab.first*bc.second-ab.second*bc.first;
if(cnt>0)return 1;
if(cnt<0)return -1;
return 0;
}
vector<int> getline(point a,point b){
int xa=a.first,ya=a.second,xb=b.first,yb=b.second;
vector<int>res= {yb-ya,xa-xb,ya*(xb-xa)-xa*(yb-ya)};
return res;
}
int main() {
int n,i,j,k;
cin>>n;
for(i=0;i<3*n;i++){
cin>>a[i].first>>a[i].second;
}
if(n==1){
point c={114514,1919810};
if(jud(c,a[0],a[1])*jud(c,a[0],a[2])==-1){
vector<int>res=getline(c, a[0]);
cout<<res[0]<<" "<<res[1]<<" "<<res[2];
return 0;
}
if(jud(c,a[1],a[2])*jud(c,a[1],a[0])==-1){
vector<int>res=getline(c, a[1]);
cout<<res[0]<<" "<<res[1]<<" "<<res[2];
return 0;
}
if(jud(c,a[2],a[0])*jud(c,a[2],a[1])==-1){
vector<int>res=getline(c, a[2]);
cout<<res[0]<<" "<<res[1]<<" "<<res[2];
return 0;
}
cout<<-1;
return 0;
}
for(i=0;i<3*n;i++){
for(j=0;j<3*n;j++){
if(i==j)continue;
int tong[3]={};
for(k=0;k<3*n;k++){
tong[jud(a[i],a[j],a[k])+1]++;
}
if(tong[0]==tong[1]&&tong[0]==tong[2]){
vector<int>res=getline(a[i], a[j]);
cout<<res[0]<<" "<<res[1]<<" "<<res[2];
return 0;
}
}
}
cout<<-1;
}
F 小红和小紫的拿球游戏
通过率:6.95%
知识点:博弈,概率
首先我们假设小紫不是随机取,那么当小红取了第一个球后,小紫每次必取和第一个球相同的颜色,小红必取和第一个球不同的颜色,这样才是最优策略。
那么小红的策略就可以确定了:首先第一次取的一定是较多的那个颜色,之后每回合都取和该球不同的颜色。若,则小红必胜;若
,那么当且仅当小紫每次都取到和小红初始颜色相同的球时小紫才能获胜。若
,则小紫除了最后一回合外,每回合必须都取到和小红初始颜色相同的球时小紫才能获胜。
参考代码:
#include<bits/stdc++.h>
using namespace std;
int main(){
int _;
cin>>_;
while(_--){
int a,b,i,j;
cin>>a>>b;
if(a<b)swap(a,b);
if(a-b>=2)cout<<1<<'\n';
else{
double p=1;
a--;
while(a&&b){
p*=1.0*a/(a+b);
a--,b--;
}
printf("%.9f\n",1-p);
}
}
}
G 小红的树上切割
通过率:2.54%
知识点:树形dp
我们定义代表:对于
号节点,它的子树切割后的红色点数量为
的方案数。
那么当维护dp转移时,有以下几种情况:
-
对于dp[u][0],讨论某孩子
,若取
,则不切;若取
,则切。
-
对于dp[u][1],讨论某孩子
,若取
,则不切;若取
,则切。另外还需要加上前面得到的dp[u][0]和dp[v][1]构成dp[u][1]的情况。
参考代码:
#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::vector<int> c(n);
for (auto &i : c) {
std::cin >> i;
}
std::vector<std::vector<int>> g(n);
for (int i = 0; i < n - 1; i++) {
int u, v;
std::cin >> u >> v;
u--, v--;
g[u].push_back(v);
g[v].push_back(u);
}
std::vector dp(n, std::vector<Z>(2));
auto dfs = [&](auto &&self, int u, int f) -> void {
dp[u][c[u]] = 1;
for (auto v : g[u]) {
if (v == f) continue;
self(self, v, u);
dp[u][1] = dp[u][1] * (dp[v][0] + dp[v][1]) + dp[u][0] * dp[v][1];
dp[u][0] *= dp[v][0] + dp[v][1];
}
};
dfs(dfs, 0, -1);
std::cout << dp[0][1] << "\n";
return 0;
}
H 小红的字符修改
通过率:5.45%
知识点:线段树/树状数组,哈希
本题考察大家对数据结构的掌握程度。假设没有修改操作,那么本题就是一个简单的字符串哈希:判断和
两段的哈希值是否相同即可。
现在加入了修改操作,我们则需要使用线段树维护哈希数组的修改:将哈希数组看成是一个进制的大数,我们每次修改即修改其中的一位,可以直接统计修改该节点对线段树每个节点带来的影响。由于本题为单点修改,因此也可以用树状数组进行维护,代码相对更简短。
参考代码:
#include <bits/stdc++.h>
template <typename T>
constexpr static T power(T a, int64_t b) {
assert(b >= 0);
T res = 1;
while (b) {
if (b & 1) {
res *= a;
}
b >>= 1;
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 index) {
if (index < pow.size()) {
return;
}
auto old_size = pow.size();
pow.resize(index * 2 + 1);
for (auto i = old_size; i <= index * 2; i++) {
pow[i] = pow[i - 1] * base;
}
}
hash_t get_pow(size_t sz) {
expand_pow(sz);
return pow[sz];
}
} // namespace Hashing
template <typename T>
struct Fenwick {
int n;
std::vector<T> a;
Fenwick(int n = 0) {
init(n);
}
void init(int n) {
this->n = n;
a.assign(n, T());
}
void add(int x, T v) {
for (int i = x + 1; i <= n; i += i & -i) {
a[i - 1] += v;
}
}
// return the sum of [0, x)
T sum(int x) {
auto ans = T();
for (int i = x; i > 0; i -= i & -i) {
ans += a[i - 1];
}
return ans;
}
// return the sum of [l, r)
T rangeSum(int l, int r) {
return sum(r) - sum(l);
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, q;
std::cin >> n >> q;
std::string s;
std::cin >> s;
Fenwick<Hashing::hash_t> hash(n);
for (int i = 0; i < n; i++) {
hash.add(i, Hashing::get_pow(i) * s[i]);
}
auto substr = [&](int l, int r) {
return hash.rangeSum(l, r) / Hashing::get_pow(l);
};
while (q--) {
int op;
std::cin >> op;
if (op == 1) {
int p;
char c;
std::cin >> p >> c;
p--;
hash.add(p, Hashing::get_pow(p) * (c - s[p]));
s[p] = c;
} else {
int l1, r1, l2, r2;
std::cin >> l1 >> r1 >> l2 >> r2;
l1--;
l2--;
std::cout << (substr(l1, r1) == substr(l2, r2) ? "Yes" : "No") << "\n";
}
}
return 0;
}