作为一名算法竞赛选手,保持训练记录和解题笔记是提升实力的重要方式。最近我在身体不适的情况下依然坚持完成了Codeforces Div3 970的虚拟参赛(VP),现将完整的解题思路和代码实现整理成这份技术笔记。本笔记不仅包含每道题的AC代码,更重要的是详细记录了解题过程中的思考路径、算法选择依据以及实际编码中遇到的坑点。
题目要求判断给定两个整数a和b,是否可以通过特定操作使它们都变为偶数。关键观察点在于:
cpp复制#include<bits/stdc++.h>
using namespace std;
void solve(){
int a,b;
cin>>a>>b;
if(a>=2&&b%2==1){
a-=2;
b++;
}
cout<<(a%2==0&&b%2==0?"YES":"NO")<<'\n';
}
注意:这题看似简单但容易忽略边界条件,比如当a正好等于2时的处理。在实际比赛中,建议先手动验证几个测试用例再提交。
题目给出一个长度为n的字符串,要求验证是否可以排列成特定规则的矩阵。解题关键在于:
cpp复制bool checkMatrix(const string& s, int n){
int l=sqrt(n);
if(l*l!=n) return false;
for(int i=0;i<n;i++){
bool isEdge = (i<l) || (i>=l*(l-1)) ||
(i%l==0) || ((i+1)%l==0);
if(isEdge && s[i]=='0') return false;
if(!isEdge && s[i]=='1') return false;
}
return true;
}
实际编码时,我最初因为着急没完全理解题意,导致一次WA。教训是:即使时间紧张,也必须完整理解题目要求再开始编码。
题目要求模拟一个特殊数列的生成过程,计算在给定区间[l,r]内能包含多少个数列元素。关键点在于:
cpp复制ll countElements(ll l, ll r){
ll d=1, ans=0;
while(l<=r){
ans++;
l += d++;
}
return ans;
}
这个问题的优化空间在于可以用数学公式替代循环计算,但在比赛时间压力下,清晰的模拟实现反而是更可靠的选择。
题目给出一个排列p和二进制字符串s,要求为每个位置i计算其所在环中'0'的数量。这题需要:
cpp复制vector<int> solveD(int n, vector<int>& p, string s){
vector<int> ans(n,-1);
for(int i=0;i<n;i++){
if(ans[i]!=-1) continue;
vector<int> cycle;
int current = i;
while(ans[current]==-1){
cycle.push_back(current);
ans[current] = 0; // 标记已访问
current = p[current]-1;
}
int zeros = count_if(cycle.begin(), cycle.end(),
[&](int x){return s[x]=='0';});
for(int x: cycle) ans[x] = zeros;
}
return ans;
}
经验:处理环状结构时,可以用染色法标记已访问节点,避免重复计算。STL的count_if能简化代码但可能影响性能,在n很大时需要谨慎使用。
题目要求处理字符串的奇偶位置字符统计,核心在于:
cpp复制struct PrefixSum{
vector<vector<int>> pre;
void build(const string& s){
// 实现奇偶前缀和
}
int query(int l, int r, int parity){
// 根据奇偶性查询
}
};
int solveE(string s){
int n = s.size();
if(n%2==0){
return calculateEvenCase(s);
}
int res = INT_MAX;
for(int i=0;i<n;i++){
string temp = s.substr(0,i) + s.substr(i+1);
res = min(res, calculateEvenCase(temp));
}
return res;
}
这个问题的教训是:预处理数据结构的选择直接影响解题效率。我在比赛中最初使用了朴素方法导致TLE,后来改用奇偶前缀和才通过。
题目要求计算数对乘积和的期望值,需要发现数学规律:
cpp复制const int MOD = 1e9+7;
ll qpow(ll a, ll b){
ll res=1;
while(b){
if(b&1) res=res*a%MOD;
a=a*a%MOD;
b>>=1;
}
return res;
}
void solveF(){
int n; cin>>n;
ll sum=0, sq_sum=0;
for(int i=0;i<n;i++){
ll x; cin>>x;
sum = (sum+x)%MOD;
sq_sum = (sq_sum+x*x)%MOD;
}
ll numerator = (sum*sum - sq_sum) % MOD;
ll denominator = (ll)n*(n-1)/2 % MOD;
ll inv_denominator = qpow(denominator, MOD-2);
cout << (numerator * inv_denominator % MOD + MOD) % MOD << '\n';
}
这个问题的关键突破点在于发现sum(aᵢaⱼ)的转换公式。建议数学题先在纸上推导清楚再编码,避免盲目尝试。
题目要求通过特定操作使数组的mex尽可能大,需要洞察到:
cpp复制void solveG(){
int n,k; cin>>n>>k;
ll g=0;
for(int i=0;i<n;i++){
ll x; cin>>x;
g = __gcd(g,x);
}
if(n==1){
cout << (k<=x ? k-1 : k) << '\n';
return;
}
vector<ll> a(n);
for(int i=0;i<n;i++) a[i] = i*g;
ll mex=0;
// 计算mex的逻辑
cout << mex << '\n';
}
这题的教训是:特殊情况必须单独处理(如n=1时)。我在比赛中因为忽略这点而WA了一次。
题目要求处理大量查询,每个查询给出x,需要找到修改后数组的中位数。优化关键在于:
cpp复制const int MAXN=1e5+10;
int cnt[MAXN], prefix[MAXN], ans[MAXN];
void preprocess(int n){
// 初始化计数数组
for(int i=1;i<=n;i++) prefix[i]=prefix[i-1]+cnt[i];
for(int x=1;x<=n;x++){
int low=0, high=x-1, res=x;
while(low<=high){
int mid=(low+high)/2;
int total=0;
for(int k=0;k*x<=n;k++){
int l=k*x, r=min(k*x+mid,n);
total += prefix[r]-prefix[l];
}
if(total > n/2){
res=mid;
high=mid-1;
}else{
low=mid+1;
}
}
ans[x]=res;
}
}
void solveH(){
int n,q; cin>>n>>q;
// 输入处理
preprocess(n);
while(q--){
int x; cin>>x;
cout<<ans[x]<<" ";
}
cout<<"\n";
}
这个问题的核心教训是:面对大数据范围的查询,预处理是唯一可行的方案。我在比赛中最初尝试在线处理每个查询,结果自然TLE。正确的做法是识别出x的范围有限(≤n),从而可以采用预处理所有可能答案的策略。
在时间紧张的比赛环境中,高效的调试技巧至关重要:
根据我的参赛经验,Div3比赛的时间分配建议:
cpp复制// 安全模板建议
#define int long long // 避免溢出
const int INF=0x3f3f3f3f;
const int MAXN=2e5+10; // 留缓冲空间
void init(){
// 每次测试用例前初始化
memset(cnt,0,sizeof(cnt));
}
根据题目特点快速选择合适算法:
在本次比赛中,H题的正确解法依赖于对问题性质的深入分析和预处理策略的选择,这比直接思考在线算法要高效得多。