【训练记录】2024年莆田市高中信息学奥赛国庆集训CSP-S提高组(第三天场外)
训练情况
rk#37
\(40 + 0 + 0 + 0 = 40\)
看到满场原题就不是很想打了TAT
赛后反思
A题错误预估了算法时间复杂度,导致了一些不必要的失分
A题
首先显然我们发现因数 \(2,5\) 能变出一个 \(10\) 来,只需要统计 \({a_i}\) 里含有因数 \(2,5\) 的个数,最后答案取小值即可。
#include<bits/stdc++.h>
#define int long long
#define double long double
using namespace std;
int n,x,cnt2,cnt5;
signed main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
freopen("zero.in","r",stdin);
freopen("zero.out","w",stdout);
cin>>n;
while(n--){
cin>>x;
if(x==0){
cout<<1;
return 0;
}
while(x%2==0){
x/=2;
cnt2++;
}
while(x%5==0){
x/=5;
cnt5++;
}
}
cout<<min(cnt2,cnt5);
return 0;
}
B题
触发关键词《最小值最大》,观察到二分单调性,显然采用二分的方法。
对于每一门武学:
如果 \(a_i < b_i\),那么这门武学全部通过自学,所需次数为 \(\lceil \frac{k}{b_i} \rceil\);
如果 \(a_i > b_i\),那么这门武学优先接受指点,如果只接受指点能达成目标 \(ma_i \ge k\),所需次数为 \(\lceil \frac{k}{b_i} \rceil\)。
否则,先接受指点,再自学,所需次数为 \(\lceil \frac{k - ma_i}{b_i} \rceil + m\)。
#include<iostream>
using namespace std;
typedef long long ll;
const int N = 3e5 + 5;
ll n, m, a[N], b[N];
// 要让每一门课都达到x的熟练度
bool check(ll x) {
// n m
ll cnt = 0;
for (int i = 1; i <= n; i++) {
if (a[i] <= b[i]) { // 必然翘课上
cnt += (x + b[i] - 1) / b[i];
} else if (m * a[i] >= x) { // 正常上课够x,那就只正常上课
cnt += (x + a[i] - 1) / a[i];
} else { // 不够的话,就先上m次a,再翘课
cnt += m + (x - a[i] * m + b[i] - 1) / b[i];
}
if (cnt > n * m) {
return false;
}
}
return cnt <= m * n;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) cin >> b[i];
ll l = 1, r = 1e18 + 5, ans;
while (l <= r) {
ll mid = (l + r) >> 1;
if (check(mid)) {
ans = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
cout << ans << endl;
return 0;
}
C题
上司参加或者不参加,员工都可以参加舞会。当上司不参加,员工参加时,他的这个员工会产生双倍的快乐值。
设计 \(DP[i][0/1]\) 分别表示 \(i\) 号参不参加舞会,\(A[i]\) 表示 \(i\) 来参加舞会时的快乐值,那么有
\[DP[i][0] = \sum_{j \in son_i} max(DP[j][0],DP[j][1] + A[j])
\]
\[DP[i][1] = A[i] + \sum_{j \in son_i} max(DP[j][0],DP[j][1])
\]
#include<bits/stdc++.h>
using namespace std;
int n,r[6005],f[6005][2],ans=0;
vector<int>v[6005];
bool book[6005];
void dfs(int root){
for(auto i:v[root]){
dfs(i);
f[root][0]+=max(f[i][1]+r[i],f[i][0]);
f[root][1]+=max(f[i][1],f[i][0]);
}
f[root][1]+=r[root],ans=max(ans,max(f[root][1],f[root][0]));
return;
}
int main(){
freopen("dance.in","r",stdin);
freopen("dance.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;++i)cin>>r[i];
for(int i=1;i<n;++i){
int uu,vv;
cin>>uu>>vv,v[vv].emplace_back(uu),book[uu]=true;
}
for(int i=1;i<=n;++i)if(!book[i]){
dfs(i),cout<<ans;
return 0;
}
return 0;
}
D题
对字典内的单词建立字典树,维护每个结点对应的总频率信息,比如某结点经过了两次,两次的单词的频率都要加上来。
查询时,可以对每一层都以深度和按键对应的字母路径做限制,都进行一次dfs,找到当前路径下出现频率最大的那个组合。
#include<iostream>
using namespace std;
const int N = 1e5 + 5;
int n, m, idx, ans;
int L[] = {
0,
0,
'a',
'd',
'g',
'j',
'm',
'p',
't',
'w'
};
int R[] = {
0,
0,
'd',
'g',
'j',
'm',
'p',
't',
'w',
'z' + 1
};
char C[N], A[N];
struct node {
int w, to[26];
}
trie[N];
void add(string p, int w) {
int len = p.length(), id = 0;
for (int i = 0; i < len; i++) {
int c = p[i] - 'a';
if (trie[id].to[c] == 0) trie[id].to[c] = ++idx;
id = trie[id].to[c];
trie[id].w += w;
}
}
string p;
int dfs(int len, int dep, int id, int sum) {
if (dep == len) {
if (sum > ans) {
for (int i = 1; i <= len; i++) A[i] = C[i];
ans = sum;
}
return 1;
}
int flag = 0;
for (int i = L[p[dep] - '0']; i < R[p[dep] - '0']; i++) {
int c = i - 'a';
if (trie[id].to[c] == 0) continue;
C[dep + 1] = i;
if (dfs(len, dep + 1, trie[id].to[c], trie[trie[id].to[c]].w)) flag = 1;
}
return flag;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) {
int w;
cin >> p >> w;
add(p, w);
}
cin >> m;
while (m--) {
cin >> p;
int e = p.length();
for (int i = 1; i < e; i++) {
ans = 0;
if (dfs(i, 0, 0, 0))
for (int j = 1; j <= i; j++) cout << A[j];
else cout << "MANUALLY";
cout << "\n";
}
cout << "\n";
}
return 0;
}