Logo admin的博客

博客

标签
暂无

CSP2023-J2模拟1004 解题报告

2023-10-04 15:08:03 By admin

徐老师的吉祥数

题解

送分题,暴力枚举即可

标程

#include <bits/stdc++.h>
using namespace std;

bool check(int x) {
    while (x > 1){
        if (x % 3 != 0){
            return false;
        }
        x /= 3;
    }
    return true;
}
int a[1000100], len = 0;
int main() {
    int l, r, ans = 0;
    scanf("%d%d", &l, &r);
    for (int i = l; i <= r; ++i){
        if (check(i)){
            ans++;
            a[len++] = i;
        }
    }
    printf("%d\n", ans);
    for (int i = 0; i < len; ++i){
        printf("%d ", a[i]);
    }
    return 0; 
}

徐老师的二进制加法

题解

想太多容易做不出来,其实这道题直接模拟计算即可

因为每次只加一个 $2^k$ 也就是二进制上只有 $1$ 个 $1$

单次进位次数最多可以达到 $n$ 次,比如 $11111111 + 1$

但是这次以后进位次数就没了,所以平均下来进位次数最多也就是 $n + m$

所以直接模拟即可,不会超时

为了方便计算可以像高精度一样把数字倒过来存

但是注意数组得多开一些,因为最高位可能会进位

至少需要开到 $n + logm$

当然了,平时习惯好的话数组稍微大一点就可以,比如开到 $1000100$ 肯定够用了

标程

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+30;
int n,q,k,ans;
bool a[N];
char c[N];

int main(){
    scanf("%d", &n);
    scanf("%s", c);
    for(int i = 0; i < n; ++i){
        a[n - i - 1] = c[i] - '0';
    }
    scanf("%d", &q);
    while(q--){
        scanf("%d", &k);
        ans = 1;
        while(a[k]){
            a[k++] = 0;
            ans++;
        }
        a[k] = 1;
        printf("%d\n", ans);
    }
    n += log2(n) + 1;
    while(!a[n]){
        n--;
    }
    for(int i = n; i >= 0; --i){
        printf("%d", a[i]);
    }
    return 0;
}

徐老师的同桌分配

题解

很容易想到贪心就是最小的配最大的,如果只求一次结果,直接排序即可,现在的问题是每新增一组就要进行一次计算

这里我们可以考虑,因为每次只在两个数组中各新增一个数字,之前的数组如果已经有序,其实可以用插入排序的方式维护一次即可,那么可以用二分找到应该插入的位置,然后插入并计算结果,但是这样的话一次插入的复杂度依旧是 $O(n)$ 的,总体复杂度 $O(n^2)$

观察题目可以发现,这道题有一个特殊的约束,就是成绩最大只有 $100$ 分,所以为了方便维护,我们完全可以直接用桶排序来做到每次插入一个数字

这样的话配对的过程就只需要枚举 $100$ 次分数即可,那么复杂度就可以降低到 $O(100 * n)$ 即可通过

标程

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5,V=105;
int n,a[N],b[N],ta[V],tb[V],tmpa[V],tmpb[V];

int qry(){
    for(int i=1;i<=100;++i)tmpa[i]=ta[i],tmpb[i]=tb[i];
    int l=1,r=100,maxn=0,tmp;
    while(!tmpa[l])l++;
    while(!tmpb[r])r--;
    while(l<=100&&r>=1){
        tmp=min(tmpa[l],tmpb[r]);
        tmpa[l]-=tmp,tmpb[r]-=tmp;
        maxn=max(maxn,l+r);
        while(!tmpa[l] && l <= 100)l++;
        while(!tmpb[r] && r >= 1)r--;
    }
    return maxn;
}

int main(){
    scanf("%d", &n);
    for(int i=1;i<=n;++i){
        scanf("%d%d", &a[i], &b[i]);
        ta[a[i]]++,tb[b[i]]++;
        printf("%d\n", qry());
    }
    return 0;
}

徐老师的炉石传说

题解

首先对于过牌的技能来说,就是让这个牌组变成了一个环,那我们可以破坏成链,把卡组复制一遍即可解决这个问题,这个也是部分分中会考虑用到的

接着考虑正解

首先我们会发现,不管是递推也好搜索也好,打牌顺序决定了能打出的牌,如果陷入这个角度可能会很难想到正解

我们从结果倒推回来观察会发现,其实能打出去的牌一定就是在 $n$ 张牌中选了一部分

比如现在选中了 $S$ 张牌,那这 $S$ 张牌要符合一个什么要求呢?

要保证这 $S$ 张牌打出的时候不会导致其他被选中的牌被弃

我们会发现,被选中的这 $S$ 张牌的 $x_i$ 之和必然是 $\leq n$ 的,这个毋庸置疑,弃牌数量总和不可能超过 $n$

接着我们把选出的这 $S$ 张牌之间的距离记为 $dis_i$,也可以发现 $\sum{dis_i} = n$

结合以上两点,我们可以确认,在这 $S$ 张牌中至少存在一张 $x_i \leq dis_i$ 的牌

即打出这张牌时不会导致下一张牌被弃,那么打出这张牌,接着剩下 $S-1$ 张牌,又回到了上述问题

所以可以证明,只要选好了 $S$ 张牌,必然存在一定的出牌顺序使得这 $S$ 张牌被打出并且互不影响

那么现在的问题就变成了,在 $n$ 张牌中选 $S$ 张牌,保证 $\sum{x_i} \leq n$ 的情况下,求最大的 $\sum{y_i}$ 这就是一个基础 $01$ 背包问题了

标程

#include<bits/stdc++.h>
using namespace std;
int n, x[1010], y[1010], dp[1010], ans;

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++){
        scanf("%d", &x[i]);
    }
    for (int i = 1; i <= n; i++){ 
        scanf("%d", &y[i]);
    }
    for (int i = 1; i <= n; i++){
        for (int j = n; j >= 0; j--){
            if (j >= x[i]){ 
                dp[j] = max(dp[j], dp[j - x[i]] + y[i]);
            }
        }
    }
    for (int i = 1; i <= n; i++){ 
        ans = max(ans, dp[i]);
    }
    printf("%d\n", ans);
    return 0;
}

CSP2023-J2模拟1003 解题报告

2023-10-03 17:15:13 By admin

徐老师的仓鼠小队

题解

贪心,先排序,然后每三个一组,用这三个的最大值减最小值。

三只一组其实和两只一组是一个意思,就是如何分组可以让每组的距离之和最小

那自然是相邻的分一组最好

标程

#include <bits/stdc++.h>
using namespace std;
int a[100005];
int main(){
    int n;
    cin >> n;
    long long ans = 0;
    for(int i = 1; i <= n; i++){
        cin >> a[i];
    }
    sort(a + 1, a + 1 + n);
    for(int i = 1; i <= n; i += 3){
        ans += a[i + 2] - a[i];
    }
    cout << ans << endl;
}

徐老师的羊腿出售

题解

首先很容易想到可以用前缀和来优化,对于 $[l,r]$ 的询问

设询问的两个字母分别是 $T_1, T_2$

如果枚举一个左端点 $a[i] == T_1$ 的话,$a[i]$ 能组成的方案数就是 $sum[T_2][r] - sum[T_2][i-1]$

但是这样的操作复杂度依旧是 $O(n^2)$,只有 $40$ 分

那么观察数据范围可以发现,必须是 $O(n)$ 或者 $O(nlogn)$

并且仔细思考为什么这道题是字母而不是数字

字母只有 $26$ 个,所以我们可以考虑能否提前处理出任意两个字母两两对应的二次前缀和

也就是说处理出 $f[T][i]$ 表示到 $i$ 为止字母 $T$ 的前缀和

然后即可处理出 $sum[T_1][T_2][i]$ 表示到 $i$ 为止的 $T_1$ 对应 $T_2$ 的方案数前缀和

那么对于每次询问,即可二分出最左侧的 $T_2$ 位置 $ql$ 和最右侧的 $T_2$ 位置 $qr$

那么就可以用 $ans = sum[T_2][T_1][qr] - sum[T_2][T_1][ql - 1]$ 来计算出区间 $[l,r]$ 内的所有 $T_2$ 对于 $T_1$ 组合的方案数

再减去 $[l,r]$ 区间内的 $T_2$ 对于 $1 \sim l - 1$ 中 $T_1$ 组合的方案数即可

最后答案为 $ans = sum[T_2][T_1][qr] - sum[T_2][T_1][ql - 1] - cnt * f[l-1][T_1]$

标程

#include<bits/stdc++.h>
using namespace std;
int n,q;
string S,T;
const int MN=2e5+5;
vector<int>pos[26],sum[26][26];
int f[MN][26];
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin>>n>>q;
    cin>>S;
    for(int i=1; i<=n; i++) {
        int t=(int)(S[i-1]-'a');
        for(int x=0; x<26; x++){
            f[i][x]=f[i-1][x];
            sum[t][x].push_back(f[i][x]);
        } 
        pos[t].push_back(i);
        f[i][t]++;
    }
    for(int t=0; t<26; t++) {
        for(int x=0; x<26; x++) {
            for(int j=1; j<sum[t][x].size(); j++){
                sum[t][x][j]+=sum[t][x][j-1];
            }
        }
    }
    while(q--) {
        int l,r;
        cin>>l>>r>>T;
        int a=T[0]-'a',b=T[1]-'a';
        int ql=lower_bound(pos[b].begin(),pos[b].end(),l)-pos[b].begin();
        int qr=upper_bound(pos[b].begin(),pos[b].end(),r)-pos[b].begin()-1;
        if(ql>qr) {
            printf("0\n");
            continue;
        }
        int cnt=qr-ql+1;
        printf("%d\n",sum[b][a][qr]-(ql==0?0:sum[b][a][ql-1])
            -f[l-1][a]*cnt);
    }
    return 0;
}

徐老师的数字拼接

题解

观察数据范围可以发现,最大只会到 $99999999$,并且 $n$ 很小,直接暴力 $dfs$ ,最后去重计数即可

标程

#include<iostream>
#include<cstdio>
#include<cassert>
#include<vector>
#include<algorithm>
using namespace std;
const int N = 15,K = 5;
int n,k;
int a[N];
bool vis[N];
int b[K];
int uppw10(int x) {
    int cnt = 1;
    while(x > 0){
        x/=10;
        cnt*=10;
    }
    return cnt;
}
vector<int>v;
void dfs(int step) {
    if(step > k) {
        int val = 0;
        for(int i= 1 ; i <= k; i++)
            val = val * uppw10(b[i]) + b[i];
        v.push_back(val);
        return;
    }
    for(int i = 1; i <= n; i++){
        if(!vis[i]) {
            vis[i] = true;
            b[step] = a[i];
            dfs(step + 1);
            vis[i] = false;
        }
    }
    return;
}
int main() {
    scanf("%d%d", &n, &k);
    for(int i = 1; i <= n; i++){
        scanf("%d", &a[i]);
    }
    dfs(1);
    sort(v.begin(),v.end());
    int ans=0;
    for(int i = 0; i < (int)v.size(); i++){
        if(i==0||v[i]!=v[i-1]){
            ans++;
        }
    }
    printf("%d",ans);
    return 0;
}

徐老师的倒水计划plus

题解

可以发现,最后保留 $m$ 个玻璃杯,这些玻璃杯的水能被保留,其他玻璃杯的水就被保留一半,然后和这 $k$ 个玻璃杯的容量总和求个 $min$ 就可以了

很容易想到的就是贪心,但是显然贪心是错误的,因为你并不知道应该选容量越大的还是原本水越多的

所以明显是个类似于背包的 dp 问题

设被选中的 $m$ 个杯子的容量总和为 $a$, 水量总和为 $b$,剩下 $n - m$ 个杯子的水量之和为 $c$

答案即为 $min(a, b + c/2)$,这里我们可以发现当 $a,b$ 确认时 $c$ 是可以直接计算的

那么我们可以设 $dp[i][j][a]$ 为前 $i$ 个杯子,选了 $j$ 个,被选玻璃杯容量和为 $a$ 时的最大水量

那么我们在转移的时候只要考虑第 $i + 1$ 个杯子选或者不选即可完成转移

标程

#include <bits/stdc++.h>
using namespace std;
int a[55], b[55], dp[55][5010];

int main() {
    int n, s = 0;
    cin >> n;
    for (int i = 1; i <= n; ++i) cin >> a[i] >> b[i], s += b[i];
    memset(dp, -0x3f, sizeof(dp));
    dp[0][0] = 0;
    for (int i = 1; i <= n; ++i)
        for (int j = i; j; --j)
            for (int k = a[i]; k <= 5000; ++k)
                dp[j][k] = max(dp[j][k], dp[j - 1][k - a[i]] + b[i]);
    for (int i = 1; i <= n; ++i) {
        double ans = 0;
        for (int j = 1; j <= 5000; ++j)
            ans = max(ans, min(1.0 * j, 0.5 * (s + dp[i][j])));
        printf("%.1f ", ans);
    }
    return 0;
}

2023初赛模拟卷(3)解析

2023-09-12 19:14:08 By admin

一、单项选择题

  1. A。计算机缺少CPU将无法正常启动。
  2. A。不妨将单位全都转成MB:36000 KB ≈ 35.2 MB,33 MB = 33 MB,0.03 GB = 30.72 MB,36500000 B ≈ 34.8 MB,所以 36000 KB 最大。
  3. C。不妨设状态 $f_i$ 表示所有高度 $\leq i$ 的二叉树的不同形态树,则可得推导公式为 $f_i=(f_{i-1}+1)^2$,所以可得 $f_1=1,f_2=4,f_3=25$,所以高度为 $3$ 的二叉树的不同形态数为 $f_3-f_2=25-4=21$。
  4. D。对于出栈序列 $e, d, b, c, a$,因为入栈序列是 $a,b,c,d,e$,在 $b,c$ 均未出栈的情况下,$e,d$ 先出栈,说明在 $e,d$,出栈时 $c$ 在 $b$ 的顶端,所以必然 $c$ 先出栈,与出栈序列矛盾,所以选项 D 错误。
  5. B。二进制数 $11001.1=$ 十进制 $1x24+1x23+0x22+0x21+1x20+1x2-1=16+8+0+0+1+0.5=25.5$。
  6. C。相同深度的二叉树中满二叉树包含的叶结点个数最多。一棵深度为 $h$ 的满二叉树具有 $2^{h-1}$ 个叶结点,因为深度为 $11$ 的满二叉树包含的叶结点个数为 $2^{10}=1024$,深度为 $12$ 的满二叉树包含的叶结点个数为 $2^{11}=2048$,且 $1024<2023 \leq 2048$,所以包含 $2023$ 个叶结点的二叉树深度至少为 $12$。

  7. A。八进制的一位对应二进制的三位,所以八进制数的后 $99$ 位均对应二进制的 $3$ 位,总位数为 $99x3=297$,最高位可能对应二进制的 $1 \sim 3$ 位(八进制 $1$ 对应二进制 $1$,八进制 $2$ 对应二进制 $10$,八进制 $3$ 对应二进制 $11$,八进制 $4$ 对应二进制 $100$,八进制 $5$ 对应二进制 $101$,八进制 $6$ 对应二进制 $110$,八进制 $7$ 对应二进制 $111$),所以一个 $100$ 位的八进制数转成二进制数可能是 $298、299$ 或 $300$ 位。

  8. B。对于 $2000$ 来说,它只包含两个不同的质因子—— $2$ 和 $5$。所以与 $2000$ 互质的数是即不包含因数 $2$ 也不包含因数 $5$的 所有数字,$2000$ 以内,包含因数 $2$ 的数共有 $2000/2=1000$ 个,包含因数 $5$ 的数共有 $2000/5=400$ 个,同时包含因数 $2$ 和 $5$ 的数(即包含因数 $10$ 的数)有 $2000/10=200$ 个,所以可得:包含因数 $2$ 或 $5$ 的数共有 $1000+400-200=1200$ 个,所以 $2000$ 以内与 $2000$ 互质的数的个数为 $2000-1200=800$ 个。
  9. A。 x & -x 可得到 $x$ 在二进制表示下最低一位 $1$ 对应的整数。
  10. A。可以将 $4$ 轮看成一个循环,每个循环执行完后,$x$ 将会 $-2$,$y$ 将会 $+2$,前 $2020$ 轮恰好 $2020/4=550$ 个循环,所以前 $2020$ 轮之后的坐标是 $(-1010,1010)$,第 $2021$ 轮 $x$ 增加了 $2021$(变成 $-1010+2021=1011$),第 $2022$ 轮 $y$ 减小了 $2022$(变成 $1010-2022=-1012$),第 $2023$ 轮 $x$ 减少 $2023$(变成 $1011-2023=-1012$)$,所以最终的坐标是 $(-1012,-1012)$。
  11. D。相当于从 $5$ 行中选两行,再从 $5$ 列中选 $2$ 列,其中 $2$ 个方格之间有 $A_2^2$ 种排列,所以总的方案数为 $C_5^2⋅C_5^2⋅A_2^2=200$。
  12. C。分析代码可得 C 错误。在比赛时考虑时间因素,也可采用“套例子”的方法,比如:设 $x=3,y=2$(期望的结果为 $2$),将其带入四个选项,可得A、B、D的结果均为 $2$,而 C 的结果为 $1$,所以选C。
  13. B。$4$ 个人之间的排列数为 $A_4^4$ ,可以想象将连续的三个空位看成一组,另一个空位看成一组,将两组空位插入 $4$ 个人之间的五个位置,方案数有 $C_5^2$ ,两组空位之间的排列数为 $A_2^2$ ,所以总的方案数为 $A_4^4⋅C_5^2⋅A_2^2=480$。
  14. B。画图即可。
  15. C。图灵奖是计算机科学领域的最高奖。

二、阅读程序

第一小题

  1. 错误。也可包含小写英文字母,因为程序中有将小写英文转成大写英文字母的实现。
  2. 正确。程序实现的功能就是将输入的字符串中的字符全部转成大写英文字母并从小到大排序后输出,所以输出的字符串长度与输入的字符串长度相同。
  3. 正确 rUiBA时,输出应为 abiru
  4. 错误。当输入为 niceToMeetYou,输出应为 ceeeimnoottuy
  5. B。当输入的字符串是升序的且仅由小写英文字母组成时,输出的字符串与输入的字符串一样。
  6. C。当输入为 codeInRUIBA 时对应的输出结果为 abcdeiinoru

第二小题

本程序使用二分查找算法寻找 $x$ 在数组 $a$ 中对应的下标 $p$(若 $x$ 在数组中不存在则 $p=-1$),同时使用 $count$ 记录二分查找的次数。

  1. 错误。解析:变量 $x$ 并不对应数组下标,所以 $x$ 的值并不会造成数组越界。
  2. 正确。解析:因为 $p$ 对应数组 $a$ 的下标,所以若找到 $a[p]==x$,则 $p$ 的值介于 $0 \sim 14$ 范围内,若没有找到,则 $p=-1$,所以 $p$ 的值不会超过 $14$。
  3. 正确。解析:变量 $count$ 的值对应二分查找的次数,对于长度为 $15$ 的数组 $a$,二分查找的次数不会超过 $⌈log_215⌉=4$。
  4. C。解析:$a[2]==5$。
  5. A。解析:$a$ 数组中不存在任何一个元素等于 $12$,所以输出的第一行为 $-1$。
  6. D。解析:一共二分查找了 $4$ 次,对应的 $mid$ 及 $a[mid]$ 分别为 $a[7]=22,a[11]=62,a[9]=45,a[8]=37$。

第三小题

  1. 正确。 设 $len$ 表示 $s$ 的二进制表示中共有多少位为 $1$,而 $cnt=2^{len}-1$,而具有 $len$ 位 $1$ 的所有二进制整数中数值最小的即为 $2^{len}-1$,所以 $cnt \le s$ 成立,或者换个简单的思路,i 永远在变小,那么最起码也是每次 $-1$,所以 $i$ 的变化次数最多就是 $s$
  2. 错误。 $bitcount(x)$ 函数计算的是 $x$ 的二进制表示中共有多少位为 $1$。
  3. 错误。数组下标对应的是 $i$ 的二进制数表示中 $1$ 出现的位数,对于 $1 \sim 10^9$ 范围内的整数,其二进制表示中 $1$ 出现的位数不会超过 $100$,所以不会发生数组越界。
  4. C。$(23)_{10}=(10111)_2$,所以 $len=4$,得 $cnt=2^{len}-1=15$。
  5. D。 $c[1]=8+2+1=11,c[2]=10+9+3=22,c[3]=11$,得 $ans=22$。
  6. B。通过分析代码可得 $c[i]=(C_{len}^i⋅i)/len⋅127$,所以 $c[1] = 127,c[2] = 762,c[3] = 1905,c[4] = 2540,c[5] = 1905,c[6] = 762,c[7] = 127$,得 $ans=2540$。

三、完善程序

第一小题

  1. B。当 $d < f(y,m)$ 时, $y$ 年 $m$ 月 $d$ 日的下一天是 $y$ 年 $m$ 月 $d+1$ 日。
  2. C。当 $d < f(y,m)$ 不成立(即 $d==f(y,m)$ )时,说明是该月的最后一天,此时若 $m < 12$,则 $y$ 年 $m$ 月 $d$ 日的下一天是 $y$ 年 $m+1$ 月 $1$ 日。
  3. C。当 $d < f(y,m)$ 和 $m < 12$ 均不成立是,说明 $m==12$ 且 $d==31$,说明是 $y$ 年的最后一天,所以它对应的下一天是 $y+1$ 年 $1$ 月 $1$日。
  4. A。
  5. A。

第二小题

  1. C。该空与下一空与快速排序的逻辑相同,易得第 (1) 空填 $j--$,也可以联系上下文对称来选择
  2. B。理由同上,当然这里可以发现,第 $11$ 行 $a[i] = a[j]$ 后,$a[j]$ 的值在逻辑上已经空出来了,这里肯定是给 $a[j]$ 赋值
  3. D。循环操作结束后能保证区间 $[l,i-1]$ 范围内的数都 $>= a[i]$,区间 $[i+1,r]$ 范围内的数都 $<= a[i]$,所以若 $i-l+1==k$,则说明 $a[i]$ 恰为区间 $[l,r]$ 范围内第 $k$ 大的数。
  4. C。若 $i-l+1 > k$,则进区间 $[l,i-1]$ 继续查找第 $k$ 大的数。
  5. C。若 $i-l+1 < k$,则区间 $[l,r]$ 范围内第 $k$ 大的是应该在区间 $[i+1,r]$ 范围内,但是因为区间 $[l,i]$ 范围内的 $i-l+1$ 个数都大于等于区间 $[i+1,r]$ 范围内的数,所以应去区间 $[i+1,r]$ 范围内找第 $k-(i-l+1)=k+l-i-1$ 大的数。

2023初赛模拟卷(2)解析

2023-09-12 19:08:49 By admin

一、单项选择题

  1. A。 C 语言是面向过程的编程语言;C++、Python、Java均为面向对象的编程语言。
  2. C。 $1GB = 1024MB = 1024 * 1024 KB = 1024 * 1024 * 1024 B = 2^30B$。
  3. A。 八进制数 $356.433$ 对应的十六进制整数是 $EE.AD8$。
  4. B。 二进制数 $00101100+00011101=01001001$。
  5. A。 栈的特点是先进后出,后进先出(First In Last Out, Last In First Out)。
  6. D。 $POP3$(邮局协议的第3个版本)、$SMTP$(简单邮件传输协议)、$IMAP$(交互式邮件存取协议)均为电子邮件中常用的协议;$P2P$对等网络是一种网络结构,与电子邮件传输无关。
  7. C。 因要求男女相间,则男生和女生的位置共有如下两种不同方案: • 男生在第 $1、3、5$ 个位置,女生在第 $2、4、6$ 个位置; • 或者女生在第 $1、3、5$ 个位置,男生在第 $1、3、5$ 个位置。 其次,男生之间的排列方案数为 $A_3^3$ 种,女生之间的排列方案数为 $A_3^3$ 种,所以总的排列方案数为 $C_2^1×A_3^3×A_3^3=72$。

  8. C。 包含 $100$ 个节点的二叉树中共有 $99$ 条边,对于任意一个节点,它若要包含 $2$ 个子节点,则需要 $2$ 条边分别连向它的两个子节点,所以最多有 $99/2=49$ 个节点具有 $2$ 个子节点。

  9. C。 快速排序算法的平均时间复杂度为 $O(nlogn)$。
  10. C。 • 以 A 开头共有 $6$ 个不同的子串,分别是 A、AB、ABB、ABBC、ABBCC、ABBCCC; • 以 B 开头共有 $5$ 个不同的子串,分别是 B、BB、BBC、BBCC、BBCCC、BC、BCC、BCCC; • 以 C 开头共有 $3$ 个不同的子串,分别是 C、CC、CCC。 所以共有 $6+8+3=17$ 个不同的非空子串。
  11. C。 $3570$ 和 $2023$ 的最大公约数是 $119$。
  12. D。基础二分查找的要求,元素有序,且能随机访问。
  13. B。不妨想思考 $0 \sim 99$ 这 $100$ 个数中包含 $2$ 的数的个数,可以发现,除了 $20 \sim 29$ 这 $10$ 个数以外,还存在 $2、12、32、42、52、62、72、82、92$ 这 $9$ 个数是包含 $2$ 的,所以 $0 \sim 99$ 范围内共有 $10+9=19$ 个包含 $2$ 的数,据此可以推导出 $100 \sim 199$ 范围内包含 $2$ 的数的个数也为 $19$,对于 $300 \sim 399,400 \sim 499,...,900 \sim 999$ 也可以发现同样的规律,但是 $200 \sim 299$ 这 $100$ 个数的百位都是 $2$, 所以这 $100$ 个数都是包含 $2$ 的数。据此可以推导出 $0 \sim 999$ 范围内包含 $2$ 的数的个数 $=100+19x9=271$,同理可得 $1000 \sim 1999$ 范围内包含 $2$ 的数的个数也为 $271$ 。而 $2000 \sim 2023$这 $24$ 个数都是包含 $2$ 的数(因为千位为 $2$),所以 $0\ sim 2022$ 范围内包含 $2$ 的数的个数为 $271x2+24=566$ ,因 $0$ 不包含 $2$,所以可得: $1 \sim 2023$ 范围内的整数中包含 $2$ 的数的个数为 $566$。
  14. B。根据函数的推导公式可得 $solve(5,6)$ 的结果为 $210$。
  15. C。解析:冯·诺依曼体系结构的核心内容是采用存储程序和程序控制原理。

二、阅读程序

第一小题

  1. 正确。解析:当 $n=1000$ 时,会使用到下标为 $1000$ 的数组元素 $a[1000]$,而数组的元素下标从 $0$ 到 $999$,所以会发生数组越界。
  2. 错误。解析:解决本题最简单的办法就是找一个“反例”,可以发现,当 $n=3$ 时,$a[1]=2,a[2]=3,a[3]=2$,并不是一个 $1 \sim n$ 的排列。
  3. 正确。解析:输入为 $5$ 时,输出的第一行为 $3$。
  4. 错误。解析:输入为 $7$ 时,输出的第一行为 $1$。
  5. D。解析:该程序的目的是将 $n$ 个数的最小值交换到 $a[1]$,将第二小的值交换到 $a[2]$,其中可能存在多个数的值均为最小值的情况,此时 $a[1]==a[2]$,如果数字都不相同则第一个数字小于第二个数字,所以答案是不确定
  6. C。解析:当 $n=20$ 时,$a$ 数组中的 $20$ 个元素分别为 $11,14,9,4,19,14,9,4,19,14,9,4,19,14,9,4,19,14,9,4$,其中次小值为 $4$。

第二小题

  1. 正确。循环过程中时刻能保证对于任意 $a[i]$,若 $i>1$,则 $a[i/2]<=a[i]$,所以 $a[1]$ 最小。
  2. 错误。是能保证 $a[i/2]$ 和 $a[i]$之间的大小关系,所以数组中一些元素的大小关系是无法确定的,比如 $a[2]$ 和 $a[3]$,所以程序并不能保证 $a[1] \sim a[n]$ 是升序的。
  3. 正确。可以发现,当 $n$ 个元素按逆序输入时,输出的 $cnt$ 是最大的,不妨设 $a[1]=10,a[2]=9,...,a[10]=1$,可以发现:输入 $a[1]$ 后交换了 $0$ 次、输入 $a[2]$ 后交换了 $1$ 次、输入 $a[3]$ 后交换了 $1$ 次、输入 $a[4]$ 后交换了 $2$ 次、输入 $a[5]$ 后交换了 $2$ 次、输入 $a[6]$ 后交换了 $2$ 次、输入 $a[7]$ 后交换了 $2$ 次、输入 $a[8]$ 后交换了 $3$ 次、输入 $a[9]$ 后交换了 $3$ 次、输入 $a[10]$ 后交换了 $3$ 次。所以总共交换了 $0+1+1+2+2+2+2+3+3+3=19$ 次。
  4. 错误。当 $n$ 个元素按逆序输入时,输出的 $cnt$ 最大,同时对于第 $i$ 个元素 $a[i]$,交换次数为 $⌊log2i⌋-1$ 次。所以: 对于第 $1$ 个数(共1个数),各交换了0次; 对于第 $2 \sim 3$ 个数(共2个数),各交换了 $1$ 次; 对于第 $4 \sim 7$ 个数(共4个数),各交换了 $2$ 次; 对于第 $8 \sim 15$ 个数(共8个数),各交换了 $3$ 次; 对于第 $16 \sim 31$ 个数(共16个数),各交换了 $4$ 次; 对于第 $32 \sim 63$ 个数(共32个数),各交换了 $5$ 次; 对于第 $64 \sim 100$ 个数(共37个数),各交换了 $6$ 次。 总的交换次数为 $1×0+2×1+4×2+8×3+16×4+32×5+37×6=480$次 $>450$ 次。
  5. D。输入 $a[3](2)$后交换了1次,输入 $a[4](4)$ 后交换了 $1$ 次,输入 $a[5](1)$ 后交换了 $2$ 次,共交换了 $4$ 次。
  6. A。只在输入 $a[3](1)$ 后交换了 $1$ 次。

第三小题

  1. 错误。 $init()$ 函数调用结束后,$m$ 的值为 $1 \sim 1000$ 范围内素数的个数,虽然我们不能很快得出这个结果,但是容易发现 $1 \sim 1000$ 范围内素数的个数肯定比非素数的个数要少,所以 $m$ 不可能是 $500$ 。
  2. 正确。 $init()$ 函数调用结束后,所以素数 $i$ 对应的 $f[i]$ 均为 $0$,所有非素数 $i$ 对应的 $f[i]$ 均为 $1$。因为 $103$ 是素数,所以 $f[103]=0$。
  3. 正确。 $g[1000]$ 表示的是 $1 \sim 1000$ 中非素数的个数, $m$ 表示的是 $1 \sim 1000$ 中素数的个数,所以 $g[1000]+m$ 等于 $1000$。
  4. B。因为 $123$ 不是素数,所以 $f[123]=1$。
  5. C。 $g[30]$ 表示 $1 \sim 30$ 范围内非素数的个数,$1 \sim 30$ 范围内共有 $20$ 个非素数,所以 $g[30]=20$。
  6. B。 $p[i]$ 表示第 $i$ 个素数,我们可以依次列举出前 $25$ 个素数:$2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97$,可得第 $25$ 个素数 $p[25]=97$。

三、完善程序

第一小题

  1. A。解析:喝了 $n$ 瓶汽水,已喝汽水总量增加$n(s+=n)$。
  2. A。解析:喝了 $n$ 瓶汽水,瓶盖总量增加 $n(t+=n)$。
  3. D。解析:用 $t$ 个瓶盖最多可兑换 $t/k$ 瓶汽水,所以 $n$ 更新为 $t/k$。
  4. B。解析:兑换尽可能多的汽水后,剩余瓶盖数量为 $t%k$。
  5. A。解析:只要能对话至少 $1$ 瓶汽水$(n>0)$,就继续循环。

第二小题

  1. C。解析:第 $i$ 小的应与第 $x+1-i$ 大数(即 $a[n-x+i]$)的比较。
  2. C。解析:根据输入是从 $a[1]$ 输入 $a[n]$,所以排序也是给 $a[1]$ 到 $a[n]$ 排序,对应的 $sort$ 函数应写为 $sort(a+1, a+n+1)$。
  3. C。解析:根据二分的写法,可知判断的区间范围是 $[l,r]$,所以二分的判断条件是 $l <= r$。
  4. B。解析:因为 $check(x)$ 判断能够凑成 $x$ 对,所以当条件成立时应让 $l = mid + 1$ 以寻找更大的答案;当条件不成立是应让 $r = mid - 1$ 以寻找更小的答案。
  5. C。

2023初赛模拟卷(1)解析

2023-09-12 19:06:51 By admin

一、单项选择题

  1. B。$8$ 位占用 $1$ 字节,所以 $64$ 位占用 $8$ 字节。
  2. C。折半查找的最大比较次数为 $⌈log21000⌉=10$次。其中 $⌈x⌉$ 表示 $x$ 向上取整的结果。
  3. C。$128$ 位等价于 $128 / 8 = 16B$ ,两张照片占用的空间为 $2×2048×2048×16B=128MB$。
  4. B。$(1011.11)2=1×23+0×22+1×21+1×20+1×2-1+1×2-2=8+0+2+1+0.5+0.25=11.75$。
  5. B。无向图中的顶点度数之和$=边数 \times 2=20+0 \times 24=40$,所以顶点个数 $= 40 / 4 = 10$。
  6. A。 徐老师(洗菜) 第 $0-15$ 分钟 第 $15-30$ 分钟 第 $30-45$ 分钟 爸爸(切菜) 第 $15-25$ 分钟 第 $30-35$ 分钟 第 $45-55$ 分钟 妈妈(烧菜) 第 $25-45$ 分钟 第 $45-65$ 分钟 第 $65-85$ 分钟 每位家庭成员完成工作的最快时间如上标所示,所以做完三道菜的最短时间为 $85$ 分钟。

  7. C。根据表达式 (a + b) * (c + d) 构造表达式树,据此可得后缀表达式为 a b + c d + *

  8. C。对于字符串 hetao: • 以 h开头的子串有 $5$ 个,分别是 h、he、het、heta、hetao(字符串本身也算其子串); • 以 e 开头的子串有 $4$ 个,分别是 e、et、eta、etao; • 以 t 开头的子串有 $3$ 个,分别是 t、ta、tao; • 以 a 开头的子串有 $2$ 个,分别是 a、ao; • 以 o 开头的子串有 $1$ 个,是 o。 初次以外,题目中没有强调非空子串,所以空串也应该算其子串。所以字符串 hetao 的子串个数为 $5+4+3+2+1+1=16$。

  9. C。按照 入栈、入栈、入栈、出栈、出栈、入栈、入栈、入栈、出栈、入栈、出栈、出栈、出栈、出栈 的顺序,入栈序列 a,b,c,d,e,f,g 对应的出栈序列为 c,b,f,g,e,d,a

  10. D。(x∨y)∧(y∨z)=(true)∧(true)=true
  11. B。$8$ 层的满二叉树的节点个数为 $2^0+2^1+...+2^7=2^8-1=255$。
  12. C。符号位为 $1$,可推出是负数,所以可以按照 补码=反码+1 的规律推出补码 $10101010$ 对应的反码为 $10101001$,再将反码的数值位取反得到原码为 $11010110$。
  13. C。画图即可
  14. B。$84$ 的哈希值为 $84 mod 11 = 7$,存放在地址空间 $7$ 中,$25$ 的哈希值为 $25 mod 11 = 3$,存放在地址空间 $3$ 中,$38$ 的哈希值为 $5$,$57$的哈希值为 $2$,$71$ 的哈希值为 $71 mod 11 = 5$,此时发现 $5$ 已经存放了数据了,那么查看 $6$ 能否存放,发现是空的,则存放在 $6$
  15. B。 $3$ 名医生分配到 $3$ 所学校课视为 $3$ 个人的排列 $A_3^3$,从 $6$ 名护士中选出 $2$ 名护士去第 $1$ 所学校的方案数为 $C_6^2$,从剩余 $4$ 名护士中选出 $2$ 名护士去第 $2$ 所学校的方案数为 $C_4^2$,剩余 $2$ 名护士去第 $3$ 所学校的方案数为 $C_2^2$,所以总的方案数为 $A_3^3×C_6^2×C_4^2×C_2^2=540$。

二、阅读程序

第一小题

  1. 错误。输入的字符串可包含出字母和数字字符以外的字符,只不过程序并不会处理。
  2. 错误。程序运行时不会发生错误,因为 s[n]=='\0',不满足任何一个 if 条件。
  3. 正确。因为输入的字符串可能包含 $ASCII$ 码大于 'z' 的 $ASCII$ 码的字符,此时对于 $cnt$ 数组来说,可能发生数组越界。
  4. 正确。因为数字字符对应的是 $cnt[0]$ 到 $cnt[9]$,所以当输入的字符串全由数字字符组成时,结果必然在 $0 \sim 9$ 范围内。
  5. C。字符串 ABCDcbaAcDbC 中字符 'C/c' 出现的次数最多( $4$次),对应的下标为 $2$ 。
  6. C。字符串 a2B3233CCDC 中字符 'C/2' 出现的次数最多( 'C'出现了 $3$ 次,'2' 出现了 $2$ 次,共 $5$ 次),对应的下标为 $2$。

第二小题

  1. 错误。由于存在大量的重叠问题,所以计算 $f(n,m)$ 的时间复杂度远超 $O(nm)$。
  2. 错误。即使去掉第 $4$ 行的代码,程序在 $m=1$ 时会返回 $n$,在 $n < 1$ 时会返回 $0$,所以不会无限递归下去。
  3. 错误。即使同时去掉第 $4$ 行和第 $5$ 行的代码,程序在 $n < 1$ 和 $m < 1$ 时也会返回 $0$,所以不会无限递归下去。
  4. D。$f(5,6)=175$。
  5. B。$f(100,2)=f(1,1)+f(2,1)+f(3,1)+...+f(99,1)=1+2+3+...+99=4950$。
  6. B。需要注意的是,当去掉第 $4$ 行的代码后,边界条件是:当 $m=0$ 时返回 $n$,否则当 $n=1$ 时,$f(n,m)$ 返回 $0$,据此计算得到 $f(3,6)=7$。

第三小题

本题基于倍增思想构造 $ST$ 表求解 $RMQ$ 问题,输出 $s[x]$ 到 $s[y]$ 中 $ASCII$ 码最大的那个字符。

  1. 错误。解析:当输入为 CGFCDBAE 2 6 时,输出为 F。需要注意的是——数组下标从 $0$ 开始计算。
  2. 正确。解析:因为 $f$ 数组的第二维只开了 $8$ 的大小,所以虽然第一维的大小是 $1000$,但是只要 $n$ 大于 $2^7=128$ 时,计算 $f$ 数组元素时就会发生数组越界。
  3. 错误。解析:因为 $ST$ 表需要先计算所有的 $f[i][0]$,再由f[i][0]推导出所有的 $f[i][1]$,再由 $f[i][1]$ 推导出所有的 $f[i][2]$,所以代码第 $19$ 行和 $20$ 行的代码的顺序不能改变。
  4. B。解析:函数 $Log2(x)$ 返回的是 $⌊log2x⌋$的结果(其中 $⌊x⌋$ 表示 $x$ 向下取整),所以 $Log2(x)$ 的返回值是 $3$。
  5. D。解析: HETAOACCEP 中 $ASCII$ 码最大的字符是 T
  6. B。解析: InHETAO 中 $ASCII$ 码最大的字符是 n,需要注意的是小写英文字母的 $ASCII$ 码都大于大写英文字母。

三、完善程序

第一小题

  1. A。需要将循环变量 $i$ 进行初始化。
  2. C。数组下标从 $0$ 到 $n-1$,所以循环的条件是 $i < n$。
  3. C。程序的目的是不断地比较 $a[i-1]$ 和 $a[i]$,若 $a[i-1] > a[i]$ 则交换 $a[i-1]$ 和 $a[i]$,所以当 $a[i-1]<=a[i]$,则不用交换,直接 $i++$ 即可。
  4. B。第 $14 \sim 16$ 行使用三步交换法交换 $a[i]$ 和 $a[i-1]$ 的数值。
  5. C。在 (5) 处填写 $i=0$ 或 $i=1$ 程序仍然能够正常运行,但是通过分析可以发现,若设 $i'$ 为目前 $i$ 变化成的最大的值,则 $a[1] \sim a[i'-1]$ 已满足升序,所以及时将 $i$ 重置为 $0$ 或 $1$,它仍会不停自增到 $i'$ 并根据条件进行交换与自减操作,所以执行 $i=0$ 或 $i=1$ 的计算量总是大于等于执行 $i--$ 的计算量,所以执行 $i--$ 是最快的方案。执行 $i++$ 是错误的。

第二小题

通过分析问题代码,可得本题的思路是先找到满足 $a[i-1] > a[i]$ 的最小的下标 $i$,然后从 $a[i]$ 到 $a[n-1]$ 中找到最大的满足 $a[j] < a[i-1]$ 的 $a[j]$,并将 $a[i-1]$ 与 $a[j]$ 交换,然后对 $a[i] \sim a[n-1]$ 从大到小排序,即实现求解 $a$ 的上一个排列。

  1. B。通过上面的分析可得 $cmp$ 函数想要实现的效果是 从大到小排,所以函数将返回 $a > b$。
  2. A。若循环结束时 $i$ 的值为 $0$,说明序列是升序的,一个升序的排列本身就是最小的排列,没有上一个排列。
  3. C。因为 $a[i]$ 到 $a[n-1]$ 是升序的,且 $a[i] < a[i-1]$,所以从 $i$ 到 $n-1$ 找到最后一个满足 $a[j] < a[i-1]$ 的 $a[j]$ 即为要交换的那个数,且 $a[j+1] > a[i-1]$。
  4. B。需要将 $a[i]$ 到 $a[n-1]$ 中最大的那个 $< a[i-1]$ 的数(即 $a[j]$)找出来与 $a[i-1]$ 交换,所以是将 $a[i-1]$ 与 $a[j]$ 进行交换。
  5. C。需要将 $a[i]$ 到 $a[n-1]$ 从大到小排序。

USACO 2021 December Contest,Gold题解

2023-06-04 20:43:15 By admin

Prob1

(Analysis by Andi Qu)

Let's call two adjacent cows "linked" if they are able to pair up with each other. We can split the cows up into chains where each pair of adjacent cows in a chain are linked, and no two cows in different chains are linked.

In each case below, we process each chain independently -- let $n$ be the length of the current chain.

Case 1: $T = 1$

For chains with an even number of cows, we can pair up all of them. For chains with an odd number of cows, we want to have exactly 1 unpaired cow. If we leave more than 1 cow unpaired, then we can split the chain into an odd-length suffix with 1 unpaired cow and an even-length prefix with all the other unpaired cows. Since the prefix has an even length, we can pair up all of its cows, which would result in a smaller sum of weights of unpaired cows.

We can thus iterate through each cow in odd-length chains, check whether it can be unpaired (removing it should not result in two odd-length chains), and finally, leave the least valuable cow unpaired.

This case can thus be solved in $\mathcal O(N)$ time.

Case 2: $T = 2$

In this case, we should try to leave unpaired cows in both even- and odd-length chains. We can use dynamic programming to solve this in $\mathcal O(N \log N)$ time.

Let $\texttt{dp}[i][j]$ be the maximum sum of values of unpaired cows if we only consider $i$ to $n$ cows in the current chain and there are $j$ unpaired ones. Let $\texttt{ub}[i]$ be the index of the leftmost cow to the right of cow $i$ that can be unpaired if cow $i$ is unpaired (or $n + 1$ if it doesn't exist). We can compute $\texttt{ub}[i]$ using binary search.

If it's possible to leave cow $i$ unpaired with $j$ unpaired cows, then $\texttt{dp}[i][j] = \max(\texttt{dp}[i + 1][j], \texttt{dp}[\texttt{ub}[i]][j - 1] + y_i)$. Otherwise, $\texttt{dp}[i][j] = \texttt{dp}[i + 1][j]$.

Since we only care about the parity of the number of unpaired cows, we can drop the second dimension of the DP array. This allows us to compute the whole DP array in $\mathcal O(N \log N)$ time (which can easily be reduced to $\mathcal O(N)$).

让我们称相邻的两头奶牛为“相关的”,如果它们能够配对。我们可以将奶牛分成链,其中每一对相邻的奶牛都是相关的,不同链中的任意两头奶牛都不相关。

在下面的每种情况中,我们独立地处理每个链 - 让$n$成为当前链的长度。

情况1:$T = 1$

对于有偶数头奶牛的链,我们可以将它们全部配对。对于有奇数头奶牛的链,我们希望恰好有1头未成对的奶牛。如果我们留下超过1头未成对的奶牛,则可以将该链拆分成一个长度为奇数的后缀,其中只有1头未成对的奶牛和一个长度为偶数的前缀,其中所有其他未成对的奶牛都在。由于前缀的长度是偶数,我们可以将它的所有奶牛配对,这将导致未成对奶牛的重量之和更小。

因此,我们可以迭代地遍历奇数长度的链中的每头奶牛,检查它是否可以独自一对(将它从链中移除不应该导致两个奇数长度的链),最后将最不有价值的奶牛单独一对。

这个情况可以在$\mathcal O(N)$的时间内解决。

情况2:$T=2$

在这种情况下,我们应尽量让偶数和奇数长度的链中都留下未成对的奶牛。我们可以使用动态规划在$\mathcal{O(N\log N)}$的时间内解决此问题。

让$\texttt{dp}[i][j]$表示如下条件下,考虑链中$i$到$n$奶牛,其中有$j$头未成对的奶牛时可以获得的最大未成对奶牛价值总和。让$\texttt{ub}[i]$表示:当第$i$头奶牛未成对时,它右侧最左边的奶牛的索引,该奶牛也能够单独一对(如果不存在这样的牛,则设为$n+1$)。我们可以使用二分查找计算$\texttt{ub}[i]$。

如果可以留下第$i$头奶牛未成对且有$j$头未成对的奶牛,则$\texttt{dp}[i][j] = \max (\texttt{dp}[i+1][j], \texttt{dp}[\texttt{ub}[i]][j-1] + y_i)$。否则,$\texttt{dp}[i][j] = \texttt{dp}[i+1][j]$。

由于我们只关心未成对的奶牛数量的奇偶性,因此可以省略DP数组的第二个维度。这使得我们可以在$\mathcal{O(N\log N)}$的时间内计算整个DP数组(通常还可以简化为$\mathcal{O(N)}$)。

Andi's code:

#include <algorithm>
#include <array>
#include <iostream>
#include <utility>
#include <vector>
using namespace std;

const int INF = 1e9;

int min_span_cost(vector<pair<int, int>>& span, int k) {
    int mn = INF;
    for (int i = 0; i < span.size(); i++) {
        if (!(i & 1) || span[i + 1].first - span[i - 1].first <= k)
            mn = min(mn, span[i].second);
    }
    return mn;
}

int max_span_cost(vector<pair<int, int>>& span, int k) {
    int n = span.size();
    if (!n) return 0;
    vector<pair<int, int>> dp(n + 1);
    dp[n] = {0, -INF};
    for (int i = n - 1; ~i; i--) {
        dp[i] = dp[i + 1];
        int ub = upper_bound(span.begin(), span.end(),
                             make_pair(span[i].first + k, INF)) -
                 span.begin();
        if (i == 0 || i == n - 1 ||
            span[i + 1].first - span[i - 1].first <= k || !(n - i & 1))
            dp[i].first = max(dp[i].first, dp[ub].second + span[i].second);
        if (i == 0 || i == n - 1 ||
            span[i + 1].first - span[i - 1].first <= k || (n - i & 1))
            dp[i].second = max(dp[i].second, dp[ub].first + span[i].second);
    }
    return (n & 1 ? dp[0].second : dp[0].first);
}

int main() {
    cin.tie(0)->sync_with_stdio(0);
    int t, n, k;
    cin >> t >> n >> k;
    int prev_x = 0, ans = 0;
    vector<pair<int, int>> curr_span;
    while (n--) {
        int x, y;
        cin >> x >> y;
        if (x - prev_x > k) {
            if (t == 1) {
                if (curr_span.size() & 1) ans += min_span_cost(curr_span, k);
            } else
                ans += max_span_cost(curr_span, k);
            curr_span.clear();
        }
        curr_span.push_back({x, y});
        prev_x = x;
    }
    if (t == 1) {
        if (curr_span.size() & 1) ans += min_span_cost(curr_span, k);
    } else
        ans += max_span_cost(curr_span, k);
    cout << ans;
    return 0;
}

Prob2

(Analysis by Andi Qu, Benjamin Qi)

To solve this problem in $\mathcal O(N^2)$ time, we can simulate the process for each $x$.

为了在$\mathcal O(N^2)$时间内解决这个问题,我们可以模拟每个$x$的过程。

#include <iostream>

int main() {
    std::cin.tie(0)->sync_with_stdio(0);
    int n, p[200001];
    std::cin >> n;
    for (int i = 0; i < n; i++) std::cin >> p[i];
    for (int x = 0; x <= n; x++) {
        int mn = 0, mx = n + 1, ans = 0;
        bool hi = false;
        for (int i = 0; i < n; i++) {
            if (p[i] > mx || p[i] < mn) continue;
            if (p[i] > x) {
                hi = true;
                mx = p[i];
            } else {
                ans += hi;
                hi = false;
                mn = p[i];
            }
        }
        std::cout << ans << 'n';
    }
    return 0;
}

Another solution that takes $\mathcal O(N^2)$ time in the worst case is to keep track of each "HI" and "LO" for each $x$, and then go through the 2 lists to find the number of "HILO" pairs. However, this solution passes the test cases with data generated uniformly at random because the expected number of "HI"s and "LO"s is $\mathcal{O}(\log N)$ for each $x$, resulting in an overall expected runtime of $\mathcal{O}(N\log N)$.

To keep track of the 2 lists, we can use 2 monotone stacks. Essentially, we maintain a sorted list of indices where Bessie says "LO". When we transition from $x$ to $x + 1$, we know that the last "LO" that Bessie will say will be to $x + 1$. If Bessie doesn't say "LO" to $k$ while thinking of $x + 1$, then she will never say "LO" to some $k$ while thinking of $y > x + 1$, so we can remove all "LO"s that used to be said after the index of $x + 1$ in the permutation.

Conveniently, the indices that we remove form a suffix of the list (because the list is sorted), so we can use a stack and pop elements from it. Since each index is pushed into and popped from the stack at most once, this takes $\mathcal O(N)$ time overall; iterating through the stack for each $x$ takes a further $\mathcal O(N)$ time per element.

Here's a C++ code snippet for finding the list of "LO"s for each $x$:

另一种在最坏情况下需要 $\mathcal O(N^2)$ 时间的解决方案是对每个$x$跟踪其每个“HI”和“LO”,然后遍历这两个列表以找到“HILO”对的数量。但是,由于每个$x$的“HI”和“LO”的期望数量是 $\mathcal{O}(\log N)$ ,因此这个解决方案在随机生成的数据测试用例中可以通过,从而导致总体期望运行时间为 $\mathcal{O}(N\log N)$。

为了跟踪这两个列表,我们可以使用 2 个 单调栈。本质上,我们维护了一个 Bessie 说“LO”的索引的排序列表。当我们从$x$转移到$x+1$时,我们知道 Bessie 最后一个“LO”是在$x+1$处说的。如果 Bessie 没有在思考$x+1$时说“LO”到$k$,那么她将永远不会在思考 $y > x + 1$ 时说“LO”到某些 $k$,因此我们可以从排列中删除在 $x+1$ 索引之后曾经说过“LO”的所有元素。

方便的是,我们需要删除的索引构成列表的后缀(因为列表是排序的),因此我们可以使用栈和弹出元素。由于每个索引最多只被推入和弹出一次,因此总共需要 $\mathcal O(N)$ 的时间;对于每个$x$迭代栈需要额外 $\mathcal O(N)$ 的时间。

以下是查找每个$x$的“LO”列表的 C++ 代码片段:

std::vector<int> los[200001];
for (int i = 1; i <= n; i++) {
    los[i] = los[i - 1];
    while (los[i].size() != 0 && los[i].back() > pos[i]) los[i].pop_back();
    los[i].push_back(pos[i]);
}

(Bonus: Find out how the rest of the test data for this problem was generated.)

To solve the problem in $\mathcal O(N)$ time, we need a way to efficiently transition from $x$ to $x + 1$.

Full Solution 1:

Claim: Let $p$ be the given permutation. If index $i$ is the "LO" in a "HILO" pair, then the "HI" in the pair must be the index $k$ such that $k < i$, $p[k] > p[i]$, and $p[k]$ is minimal.

Proof: If no such $k$ exists, then there is no greater element before $i$, so $i$ can't be the "LO" in a "HILO" pair. If Bessie says "LO" to $p[k]$, then Elsie will not guess $p[i]$, so $i$ can't be the "LO" in a "HILO" pair. Otherwise, the last "HI" that Bessie says, before saying "LO" to $p[i]$, must be to $p[k]$.

Knowing this, we can compute an array $\texttt{prv}$ where $\texttt{prv}[i] = k$ as described above using a monotone stack.

Claim: Let index $j$ be the last "LO" before Bessie says "LO" to $p[i]$. For each $x$ where Elsie doesn't skip $p[i]$, $j$ is the same.

Proof: Let $j_1 < j_2 < i$ and $p[j_1], p[j_2] < p[i]$. If Bessie says "LO" to $p[i]$, then there are 3 possibilities:

  1. Bessie never says "LO" to either $p[j_1]$ or $p[j_2]$ because she already said "LO" at some index $k < j_1$ where $p[k] > p[j_1], p[j_2]$.
  2. Bessie always says "LO" to $p[j_1]$ but never to $p[j_2]$ because she said "LO" at some index $j_1 \leq k < j_2$ where $p[k] > p[j_2]$.
  3. Bessie always "LO" to both $p[j_1]$ and $p[j_2]$ because neither of the above happen.

If transitioning causes Bessie to stop saying "LO" to some index before $i$, then it must also cause her to stop saying "LO" to $p[i]$ too. Therefore, $j$ is the same for each $p[i]$.

Claim: Let $j$ be defined as above. Index $i$ is the "LO" in a "HILO" pair if and only $\texttt{prv}[i]$ exists, and $j$ doesn't exist or $\texttt{prv}[i] \neq \texttt{prv}[j]$.

Proof: If $\texttt{prv}[i]$ doesn't exist, then Bessie never says "HI" before saying "LO" to $p[i]$, so it can't be the "LO" in a "HILO" pair. Otherwise, if $\texttt{prv}[i] \neq \texttt{prv}[j]$, then the last "HI" that Bessie says before saying "LO" to $p[i]$ must be after saying "LO" to $p[j]$ by definition. In this case, index $i$ is the "LO" in a "HILO" pair.

Knowing this, we can then determine, once for each index, whether it's the "LO" in a "HILO" pair, and thus how many "HILO" pairs there are for each $x$.

为了在 $\mathcal O(N)$ 时间内解决这个问题,我们需要一种有效地从 $x$ 过渡到 $x+1$ 的方法。

全面解决方案 1:

声明: 令 $p$ 为给定的排列。如果索引 $i$ 是“HILO”对中的“LO”,则该对中的“HI”必须是索引 $k$,满足 $k < i$,$p[k] > p[i]$,且 $p[k]$ 是最小的。

证明: 如果不存在这样的 $k$,则在 $i$ 之前没有更大的元素,因此 $i$ 不能是“HILO”对中的“LO”。如果 Bessie对 $p[k]$ 说“LO”,则Elsie不会猜测 $p[i]$,因此 $i$ 不能是“HILO”对中的“LO”。否则,在Bessie最后一次说“LO”之前,她所说的最后一个“HI”必须是对 $p[k]$。

了解这些,我们可以使用单调栈计算一个数组 $\texttt{prv}$,其中 $\texttt{prv}[i]=k$,其中 $k$ 如上所述,以有效地从 $x$ 过渡到 $x+1$。

声明: 索引 $j$ 是 Bessie 在对 $p[i]$ 说“LO”之前的最后一个“LO”。对于 Elsie 不跳过 $p[i]$ 的每个 $x$,$j$ 都是相同的。

证明: 令 $j_1 < j_2 < i$,且 $p[j_1],p[j_2] < p[i]$。如果 Bessie 对 $p[i]$ 说“LO”,则有三种可能:

  1. Bessie从未对$p[j_1]$或$p[j_2]$说“LO”,因为她已经在一些索引$k< j_1$处说过“LO”,且$p[k]>p[j_1],p[j_2]$。

  2. Bessie总是对$p[j_1]$说“LO”,但不对$p[j_2]$说,因为她在某个索引$j_1\leq k < j_2$处说过“LO”,且$p[k]>p[j_2]$。

  3. Bessie总是同时对$p[j_1]$和$p[j_2]$说“LO”,因为以上两种情况都未发生。

如果从$x$过渡导致Bessie在$i$之前停止对某个索引说“LO”,则它也必须导致她停止对$p[i]$说“LO”。因此,$j$对于每个$p[i]$是相同的。

声明: 如上所述定义$j$。若存在$\texttt{prv}[i]$且$j$不存在或$\texttt{prv}[i]\neq\texttt{prv}[j]$,则索引$i$是“HILO”对中的“LO”。

证明: 如果$\texttt{prv}[i]$不存在,则Bessie在对$p[i]$说“LO”之前从未说过“HI”,因此它不能是“HILO”对中的“LO”。否则,如果$\texttt{prv}[i]\neq\texttt{prv}[j]$,则Bessie在对$p[i]$说“LO”之前最后说的“HI”一定是在按定义对$p[j]$说“LO”之后。在这种情况下,索引$i$是“HILO”对中的“LO”。

知道了这些,我们可以对于每个索引确定它是否是“HILO”对中的“LO”,从而确定每个$x$中有多少“HILO”对。

Andi's code:

#include <iostream>
#include <stack>

int main() {
    std::cin.tie(0)->sync_with_stdio(0);
    int n, pos[200001], prv[200001];
    bool hilo[200001];
    std::cin >> n;
    for (int i = 1; i <= n; i++) {
        int p;
        std::cin >> p;
        pos[p] = i;
    }

    std::stack<int> stck;
    stck.push(0);
    for (int i = n; i > 0; i--) {
        while (stck.top() > pos[i]) stck.pop();
        prv[pos[i]] = stck.top();
        stck.push(pos[i]);
    }
    while (stck.size() != 1) stck.pop();

    std::cout << "0n";
    int cnt = 0;
    for (int i = 1; i <= n; i++) {
        while (stck.top() > pos[i]) {
            cnt -= hilo[stck.top()];
            stck.pop();
        }
        hilo[pos[i]] = prv[pos[i]] != 0 &&
                       (stck.top() == 0 || prv[pos[i]] != prv[stck.top()]);
        stck.push(pos[i]);
        cnt += hilo[pos[i]];
        std::cout << cnt << 'n';
    }
    return 0;
}

Full Solution 2: Another way we can solve this problem in $\mathcal O(N \log N)$ time is with stacks plus a sorted set.

First, we compute, for each $x \rightarrow x + 1$ event, which elements of the permutation start and stop becoming "HI"s and "LO"s. We can do this with a stack.

Next, we process the events for each $x$. We can maintain an ordered map of "HI"s and "LO"s, and inserting or erasing any element from that map changes a constant number of "HILO" pairs. Using C++'s iterators, we can process these changes efficiently.

完整解答 2: 另一种我们可以在$\mathcal O(N \log N)$时间内解决这个问题的方式是使用堆栈加上一个排序的集合。

首先,我们对于每个$x \rightarrow x + 1$事件,计算开始和停止成为“HI”和“LO”的置换元素。我们可以使用一个堆栈来完成这个任务。

接下来,我们处理每个$x$的事件。我们可以维护一个有序映射,包含“HI”和“LO”。插入或删除该映射中的任何元素会改变一定数量的“HILO”对。使用C++的迭代器,可以有效地处理这些更改。

Ben's code:

#include <bits/stdc++.h>
using namespace std;

template <class T> using V = vector<T>;

int main() {
    cin.tie(0)->sync_with_stdio(0);
    int N;
    cin >> N;
    V<int> P(N);
    for (auto &t : P) {
        cin >> t;
        --t;
    }
    V<int> pos(N);
    for (int i = 0; i < N; ++i)
        pos[P[i]] = i;
    V<V<pair<int, char>>> to_ins(N + 1);
    V<V<int>> to_del(N + 1);
    { // process "LO"s from lowest to highest, record insertions and deletions
        stack<int> cur;
        for (int i = 0; i < N; ++i) {
            while (!cur.empty() && cur.top() > pos[i]) {
                to_del.at(i + 1).push_back(cur.top());
                cur.pop();
            }
            cur.push(pos[i]);
            to_ins.at(i + 1).push_back({pos[i], 'L'});
        }
    }
    { // process "HI"s from highest to lowest, record insertions and deletions
        stack<int> cur;
        for (int i = N - 1; i >= 0; --i) {
            while (!cur.empty() && cur.top() > pos[i]) {
                to_ins.at(i + 1).push_back({cur.top(), 'H'});
                cur.pop();
            }
            cur.push(pos[i]);
            to_del.at(i + 1).push_back(pos[i]);
        }
        while (!cur.empty()) {
            to_ins.at(0).push_back({cur.top(), 'H'});
            cur.pop();
        }
    }
    int ans = 0;
    map<int, char> m; // maps each position to 'H' or 'L'
    auto hilo = [&](map<int, char>::iterator it,
                    map<int, char>::iterator next_it) {
        return it->second == 'H' && next_it->second == 'L';
    };
    auto get_dif = [&](map<int, char>::iterator it) {
        int dif = 0;
        if (it != begin(m))
            dif += hilo(prev(it), it);
        if (next(it) != end(m))
            dif += hilo(it, next(it));
        if (it != begin(m) && next(it) != end(m))
            dif -= hilo(prev(it), next(it));
        return dif;
    };
    for (int i = 0; i <= N; ++i) { // to finish, go from lowest to highest again
        for (auto &t : to_del.at(i)) {
            auto it = m.find(t);
            assert(it != end(m));
            ans -= get_dif(it);
            m.erase(it);
        }
        for (auto &t : to_ins.at(i)) {
            auto it = m.insert(t).first;
            assert(it->second);
            ans += get_dif(it);
        }
        cout << ans << "n";
    }
}

Full Solution 3: Here is a simpler solution that also runs in $\mathcal O(N\log N)$. This one doesn't explicitly make use of monotonic stacks, though it is possible to modify it to run in $\mathcal O(N)$ time with them.

完整解答 3: 这是一种更简单的解决方案,也可以在$\mathcal O(N\log N)$时间内运行。这种方法没有明确地使用单调栈,但是可以通过修改它们来使其在$\mathcal O(N)$时间内运行。

首先,我们使用一个数组$tot$来记录$h_1, h_2, \ldots, h_n$中每个元素的前缀和。我们还用一个$set$来存储之前遇到的每个$h_i$值,以便我们可以轻松查找比当前$h_i$值小的元素数量和比当前$h_i$值大的元素的数量。这可以使用$set$的$lower\_bound$函数进行。

我们现在可以遍历$h$数组,对于每个$h_i$值,我们可以计算小于$h_i$的值的数量和大于$h_i$的值的数量。这些数量可以通过查询set中的两个边界得到。我们可以将这些数量相乘,加入到答案中。然后将$h_i$插入到$set$中。

时间复杂度为$\mathcal O(N\log N)$,由于我们需要用$set$来存储过去的$h_i$值。如果我们将该set替换为单调栈,可以在$\mathcal O(N)$时间内求解该问题。

Allen Wu's code:

#include <iostream>
#include <map>
#include <set>
#include <utility>
#include <vector>
using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    int n;
    cin >> n;
    vector<int> arr(n);
    for (int i = 0; i < n; ++i)
        cin >> arr[i];
    vector<int> pos(n + 1);
    for (int i = 0; i < n; ++i)
        pos[arr[i]] = i;
    map<int, int> existing;
    vector<int> changes(n + 1);
    for (int i = 0; i < n; ++i) {
        int num = arr[i];
        // goal is to compute how the number of HILOs changes
        // when we go from x = num-1 to x = num
        auto it = existing.upper_bound(num);
        if (it != existing.end()) {
            // add one if num is involved in a HILO when x = num
            if (it == existing.begin())
                ++changes[num];
            else if (it->second > prev(it)->second)
                ++changes[num];
        }
        // subtract one if num is involved in a HILO when x = num-1
        if (pos[num - 1] > pos[num])
            --changes[num];
        existing[num] = i;
    }
    int sum = 0;
    for (int i = 0; i <= n; ++i) {
        sum += changes[i];
        cout << sum << "n";
    }
}

Prob3

(Analysis by Andi Qu)

For each sub-case, we need to check 4 conditions:

Condition 1: Every color appears in a contiguous range of $x$'s.

We can check this condition by maintaining an array that describes at which $x$ a color last appeared. If the last $x$ is always 1 less than the current $x$, then this condition holds.

Sub-case 2 in the example does not satisfy this condition.

Condition 2: For each $x$, if one appearance of color $a$ is after the first appearance and before the second appearance of color $b$, then the other appearance of color $a$ must do the same.

Let's say that a color becomes "activated" when it appears for the first time, and then "deactivated" when it appears the second time. We can then check this condition by maintaining a stack of active colors. When a color becomes deactivated, it must be at the top of the stack. If this is always true, then this condition holds.

Sub-case 3 in the example does not satisfy this condition.

If the 2 appearances of color $a$ are between the 2 appearances of color $b$, then we know that bracelet $a$ must be contained inside bracelet $b$. This hierarchical relationship allows us to transform the bracelets into a tree structure, where:

  • The nodes are the bracelets.
  • If a bracelet is contained by another bracelet, then its parent is the innermost bracelet that contains it.
  • Otherwise, its parent is a placeholder node (e.g. node $0$).

Condition 3: Each node's parent in the tree must be consistent for every $x$ it appears in.

This is because if some color appears, then its parent must also appear. We can check this condition by maintaining an array describing the parents of colors we've seen before. We can then check this array against the input for each $x$.

Condition 4: The ordering of colors in the input must be consistent for every $x$ it appears in.

We can check this condition by maintaining 2 lists -- one for the current $x$'s color order and the other for all previous $x$'s' color order. To check the ordering, we can compare the ordering of these two lists' intersection, and then merge the lists after processing $x$ by using 2 pointers. Since the constraints are so small, straightforward list insertion is fast enough for this.

Sub-case 5 in the example does not satisfy this condition.

Andi's code (which runs in $\mathcal{O}(N^2M)$ time).

对于每个子问题,我们需要检查四个条件:

条件1: 每种颜色都出现在$x$的一个连续范围内。

我们可以通过维护一个数组来检查此条件,该数组描述每种颜色上次出现在哪个$x$。如果最后一个$x$始终比当前$x$小1,则符合此条件。

例如,示例中的子问题2不满足此条件。

条件2:对于每个$x$ ,如果颜色$a$的一个出现在颜色$b$的第一次出现之后且第二次出现之前,则颜色$a$的另一个出现也必须满足同样的条件。

我们可以使用“活动”颜色的堆栈来检查此条件。颜色第一次出现时,它被激活,当它第二次出现时被“取消激活”。然后,当一个颜色被“取消激活”时,它必须在堆栈的顶部。如果始终如此,则符合此条件。

示例中的子问题3不满足此条件。

如果颜色$a$的两次出现都在颜色$b$的两次出现之间,则我们知道手镯$a$必须包含在手镯$b$中。这种层次关系允许我们将手镯转换为树结构,其中:

  • 节点是手镯。
  • 如果一个手镯被另一个手镯包含,则其父节点是包含它的最内层手镯。
  • 否则,其父节点是一个占位符节点(例如节点0)。

条件3: 树中每个节点的父节点必须在它出现的每个$x$中保持一致。

这是因为如果某种颜色出现,则其父节点也必须出现。我们可以通过维护一个描述之前看到的颜色的父节点的数组来检查此条件。然后,我们可以将此数组与每个$x$的输入进行比较,以检查该条件是否成立。

条件4: 输入中颜色的顺序必须在其出现的每个$x$中保持一致。

我们可以通过维护两个列表来检查此条件——一个用于当前$x$的颜色顺序,另一个用于所有之前$x$的颜色顺序。为了检查顺序,我们可以比较这两个列表的交集的顺序,并使用两个指针在处理完$x$后合并列表。由于约束非常小,直接列表插入对于这个问题来说已经足够快了。

示例中的子问题5不满足此条件。

Andi的代码的时间复杂度为$\mathcal{O}(N^2M)$。

#include <algorithm>
#include <iostream>
#include <stack>
#include <vector>
using namespace std;

int main() {
    cin.tie(0)->sync_with_stdio(0);
    int t, last_appeared[51], parent[51];
    cin >> t;
    while (t--) {
        int n, m;
        bool good = true;
        vector<int> glob_order;
        cin >> n >> m;
        for (int i = 0; i <= n; i++) last_appeared[i] = parent[i] = -1;

        while (m--) {
            int k, curr = 0;
            cin >> k;
            stack<int> stck;
            vector<int> curr_order;

            while (k--) {
                int c;
                cin >> c;
                if (!good) continue;
                if (last_appeared[c] == m && stck.top() == c) {
                    stck.pop();
                    curr = parent[curr];
                } else {
                    if ((~last_appeared[c] && last_appeared[c] != m + 1) ||
                        last_appeared[c] == m ||
                        (~last_appeared[c] && parent[c] != curr)) {
                        // Conditions 1, 2, and 3
                        good = false;
                        continue;
                    }
                    // Condition 4 part A
                    curr_order.push_back(c);

                    parent[c] = curr;
                    last_appeared[c] = m;
                    stck.push(c);
                    curr = c;
                }
            }
            // Condition 4 part B
            if (!good) continue;
            int ptr = 0;
            for (int i : curr_order) {
                while (ptr != glob_order.size() &&
                       !count(curr_order.begin(), curr_order.end(), glob_order[ptr]))
                    ptr++;
                if (!count(glob_order.begin(), glob_order.end(), i))
                    glob_order.insert(glob_order.begin() + ptr, i);
                else if (ptr == glob_order.size() || glob_order[ptr] != i) {
                    good = false;
                    break;
                }
                ptr++;
            }
        }
        cout << (good ? "YESn" : "NOn");
    }
    return 0;
}

Of course, it is possible to solve this problem in $\mathcal{O}(NM)$ time, but it was not necessary to do so due to the low constraints.

当然,可以在$\mathcal{O}(NM)$的时间复杂度内解决这个问题,但是由于约束条件较低,没有必要这样做。

USACO 2022 December Contest,Gold题解

2023-06-04 19:51:20 By admin

Prob1

(Analysis by Timothy Feng)

Define $\text{dp}[i][j][k]$ to be the maximum amount of popularity Bessie can achieve with her friends $1 \ldots i$, $j$ moonies, and $k$ ice cream cones.

  • If Bessie does not want to bribe cow $i$, then we can update $\text{dp}[i+1][j][k] = \text{dp}[i][j][k]$.
  • If Bessie chooses to bribe cow $i$, she can optionally spend some ice cream cones to decrease her cost. Loop through $0 \ldots k$ to brute force how many ice cream cones Bessie will spend on cow $i$. If Bessie chooses to spend $c$ cones, then Bessie needs to spend $C_i - \lfloor \frac{c}{X_i} \rfloor$ moonies. Therefore, $\text{dp}[i + 1][j - (C_i - \lfloor \frac{c}{X_i} \rfloor)][k - c] = \text{dp}[i][j][k]$.

However, this code runs in $\mathcal{O}(NAB^2)$ time.

To do better, suppose that we already know the set of cows that we plan to take. How do we check that inviting these cows is within our budget? We can do this greedily. Start by not spending any cones at all, and spending only money to invite these cows. This might cost more money than we have. Next, we will try to spend some ice cream cones to reduce the amount of money we need to spend. Note that at this point, we would always choose the cow with the smallest $X_i$ to decrease the total cost most efficiently. In other words, the cows that we bribe with cones is a prefix of all cows when sorted by $X_i$. This observation leads us to the fact that for each $j$ and $k$, to choose a new cow $i$, we only have one transition to consider. Sort Bessie's friends by increasing $X_i$. Note that if we take cow $i$, we want to spend all our ice cream cones first before we move on to spending money, so we would use $c = \min(k, C_i\cdot X_i)$ cones and $C_i - \lfloor \frac{c}{X_i} \rfloor$ moonies. Due to the $\mathcal{O}(NAB)$ states we have, this results in an $\mathcal{O}(NAB)$ time dp.

We can further remove one dimension. By the same observation from before - that cones are used to the maximum before moonies - for all dp states, either $k$ equals zero or $j$ equals $A$. For each $i$, we now only consider $\mathcal{O}(A + B)$ states, leading us to our final $\mathcal{O}(N(A+B))$ solution.

定义 $\text{dp}[i][j][k]$ 为贝茜与她的 $1 \ldots i$ 个朋友、$j$ 个月之神和 $k$ 个冰淇淋获得的最大人气值。

  • 如果贝茜不想收买第 $i$ 头奶牛,那么我们可以更新 $\text{dp}[i+1][j][k] = \text{dp}[i][j][k]$。
  • 如果贝茜选择行贿第 $i$ 头奶牛,她可以选择花费一些冰淇淋来降低成本。循环遍历 $0 \ldots k$ 来暴力计算贝茜将在第 $i$ 头奶牛身上花费多少冰淇淋。如果贝茜选择花费 $c$ 个冰淇淋,则她需要花费 $C_i - \left\lfloor \frac{c}{X_i} \right\rfloor$ 个月之神进行收买。因此,$\text{dp}[i + 1][j - (C_i - \left\lfloor \frac{c}{X_i} \right\rfloor)][k - c] = \text{dp}[i][j][k]$。

然而,这份代码的时间复杂度为 $\mathcal{O}(NAB^2)$。

为了更好地解决问题,假设我们已经知道要考虑的奶牛集合。我们如何检查邀请这些奶牛是否在我们的预算内?我们可以贪心地解决这个问题,从不花费任何冰淇淋开始,只花费金钱邀请这些奶牛,这可能会花费更多金钱。接下来,我们将尝试花费一些冰淇淋来减少我们需要花费的金钱。注意,在这个阶段,我们总是选择 $X_i$ 最小的奶牛来最有效地降低总成本。换句话说,通过 $X_i$ 排序之后,使用冰淇淋贿赂的奶牛是所有奶牛的前缀。这个观察让我们得出一个结论:对于每个 $j$ 和 $k$,选择一个新的奶牛 $i$,我们只有一个转移需要考虑。按 $X_i$ 递增排序贝茜的朋友。注意,如果我们选择奶牛 $i$,我们会先花光所有的冰淇淋,然后再花费金钱,所以我们会使用 $c =\min(k, C_i \cdot X_i)$ 份冰淇淋和 $C_i - \lfloor \frac{c}{X_i} \rfloor$ 月之神的费用。由于我们拥有 $\mathcal{O}(NAB)$ 种状态,因此这导致了一个 $\mathcal{O}(NAB)$ 的时间复杂度。

我们可以进一步去掉一个维度。根据之前的观察,冰淇淋在花尽之前被最大限度地使用,因此对于所有的dp状态,$k$ 要么等于 $0$,要么 $j$ 等于 $A$。对于每个 $i$,我们现在只考虑 $\mathcal{O}(A + B)$ 个状态,这使我们得出最终的 $\mathcal{O}(N(A+B))$ 解。

Timothy's C++ code:

#include <bits/stdc++.h>
using namespace std;

const int N = 2000 + 1;

int dp[N][2 * N];

void set_max(int &a, int b) {
    if (b > a) a = b;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, moonie, cones;
    cin >> n >> moonie >> cones;

    vector<array<int, 3>> cows(n);
    for (auto &[x, p, c] : cows) {
        cin >> p >> c >> x;
    }
    sort(cows.begin(), cows.end());

    memset(dp, -1, sizeof dp);

    dp[0][moonie + cones] = 0;
    for (int i = 0; i < n; ++i) {
        auto [x, p, c] = cows[i];
        for (int j = 0; j <= moonie + cones; ++j) {
            if (dp[i][j] == -1) continue;

            set_max(dp[i + 1][j], dp[i][j]);
            if (j - c * x >= moonie) {
                set_max(dp[i + 1][j - c * x], dp[i][j] + p);
            } else if (j > moonie) {
                int cost_left = c - (j - moonie) / x;
                if (moonie - cost_left >= 0)
                    set_max(dp[i + 1][moonie - cost_left], dp[i][j] + p);
            } else if (j <= moonie && j - c >= 0) {
                set_max(dp[i + 1][j - c], dp[i][j] + p);
            }
        }
    }

    cout << *max_element(dp[n], dp[n] + moonie + cones + 1) << "n";
}

Nick Wu's Python code:

n, a, b = (int(x) for x in input().split())
dpmoney = [0] * (a+1)
dpcones = [0] * (b+1)
v = sorted([[int(x) for x in input().split()] for _ in range(n)], key = lambda x: x[2])
for p, c, x in v:
  for i in range(a-c+1):
    dpmoney[i] = max(dpmoney[i], dpmoney[i+c] + p)
  for i in range(max(0, a-c+1), min(a+1, a-c+1 + (b // x))):
    conesneed = (i-(a-c)) * x
    dpmoney[i] = max(dpmoney[i], dpcones[conesneed] + p)
  for i in range(b-x*c+1):
    dpcones[i] = max(dpcones[i], dpcones[i+x*c] + p)
    dpmoney[a] = max(dpmoney[a], dpcones[i])
print(dpmoney[0])

Prob2

(Analysis by Joe Li, Larry Xing, Benjamin Qi)

We first present a naive solution.

Let's fix the mountain $i$ that Bessie is standing on, and consider which mountains she can see. If she can see mountain $j > i$, that means that for all $i < k < j$, $\frac{h_k-h_i}{k-i} \leq \frac{h_j-h_i}{j-i}$. Thus, for each $i$, we can calculate how many $j$ satisfy this property by sweeping from left to right. We repeat this process after every update, yielding a time complexity of $O(QN^2)$.

然后我们介绍一种朴素的解法。

首先我们固定贝茜站在哪座山上,考虑她能看到哪些山。如果她能看到 $j>i$ 的山,那么对于所有 $i< k < j$,$\frac{h_k-h_i}{k-i} \leq \frac{h_j-h_i}{j-i}$。 因此,对于每个 $i$,我们可以通过从左到右扫描来计算有多少个 $j$ 满足这个条件。我们每次更新后重复这个过程,得到 $O(QN^2)$ 的时间复杂度。

However, we can optimize this by precampiong the slopes $\frac{h_j - h_i}{j-i}$. For each $i$, suppose the minimum slope is $\frac{h_{j_i} - h_i}{j_i-i}$. Then, for all $j < j_i$, we know that Bessie cannot see mountain $j$. When we update the height of mountain $i$, we can update the value of $j_i$, and also check if any $j < j_i$ should now be visible.

This gives us a time complexity of $O(QN\log N)$, since updating the slope and maintaining the sorted order requires a binary search (or equivalent data structure). In particular, during each query, we can binary search for $j_i$ in $O(\log N)$ time, and updates take $O(\log N)$ time as well.

然而,我们可以通过预处理斜率 $\frac{h_j - h_i}{j-i}$ 来进行优化。 对于每个 $i$,假设最小斜率是 $\frac{h_{j_i} - h_i}{j_i-i}$。 那么对于所有 $j < j_i$,我们知道贝茜看不到山 $j$。 当我们更新山 $i$ 的高度时,我们可以更新 $j_i$ 的值,并检查 是否有任何 $j < j_i$ 现在可以看到。

这使我们的时间复杂度为 $O(QN\log N)$,因为更新斜率并维护排序需要进行二分查找 或等效的数据结构。特别地,在每个查询期间,我们可以在 $O(\log N)$ 的时间内进行二分查找来寻找 $j_i$, 并且更新也需要 $O(\log N)$ 的时间。

Joe's code:

#include <bits/stdc++.h>
using namespace std;

void fastIO() {
    ios_base::sync_with_stdio(false);
    cin.tie(0);
}

typedef long long ll;

int N;
ll h[5100];

int main() {
    fastIO();
    cin >> N;
    for (int i = 1; i <= N; i++) { cin >> h[i]; }
    int Q;
    cin >> Q;
    for (int i = 1; i <= QN; i++) {
        int x, y;
        cin >> x >> y;
        h[x] += y;
        int ans = 0;
        for (int j = 1; j <= N; j++) {
            ll bh = 0, bd = -1;
            for (int k = j + 1; k <= N; k++) {
                if (bd == -1) {
                    ans++;
                    bd = 1, bh = h[k] - h[j];
                } else if ((ll)(h[k] - h[j]) * bd >= (ll)bh * (k - j)) {
                    ans++;
                    bd = k - j, bh = h[k] - h[j];
                }
            }
        }
        cout << ans << "n";
    }
}

To speed this up, we can maintain a "monotonic set" for each index $i$. In the $i$th set, we store in sorted order all indices $j$ such that for all $i < k < j$, $\frac{h_k-h_i}{k-i} \leq \frac{h_j-h_i}{j-i}$. When we perform an update at an index $x$, we do the following:

  • For $i < x$, because the updates always increase the height of a mountain, the value of $\frac{h_x-h_i}{x-i}$ increases. So we may need to insert $x$ into the monotonic set for $i$ if it is now visible from $i$, and delete any indices greater than $x$ no longer visible from $i$. For each $i$, this may be done in $O(\log N)$ amortized time, for a total of $O(N\log N)$ amortized time.
  • For $i = x$, we can naively reupdate the entire monotonic set for $i$, which takes $O(N)$ or $O(N\log N)$ time.
  • For $i > x$, the update does not affect the monotonic sets.

Thus, we can perform each update in $O(N\log N)$. Initially, we perform the process described in the naive solution once to initialize the monotonic sets in $O(N^2\log N)$ amortized time. Therefore, the total time complexity is $O(N^2\log N+QN\log N)$ or $O(N^2 + QN\log N)$ depending on whether the set you are using supports removing a range of $c$ consecutive elements in $O(c+\log N)$ time.

Note: to avoid using doubles to compare two fractions $\frac{a}{b}$ and $\frac{c}{d}$, we can instead compare $a\cdot d$ and $b \cdot c$.

然而,我们可以通过预处理斜率 $\frac{h_j-h_i}{j-i}$ 来进行优化。对于每个 $i$,假设最小斜率为 $\frac{h_{j_i}-h_i}{j_i-i}$。那么对于所有 $j

为了加速这个过程,我们可以为每个索引 $i$ 维护一个“单调集合”(monotonic set)。在第 $i$ 个集合中,我们按顺序存储一些索引 $j$,满足对于所有 $i < k < j$ 都有 $\frac{h_k-h_i}{k-i} \leq \frac{h_j-h_i}{j-i}$。当我们在索引 $x$ 处执行更新时,我们可以执行以下操作:

  • 对于 $i
  • 对于 $i=x$,我们可以简单地重新更新整个单调集合,需要 $O(N)$ 或 $O(N\log N)$ 的时间。
  • 对于 $i>x$,更新不会影响单调集合。

因此,我们可以在 $O(N\log N)$ 的时间内完成每次更新。最初,我们可以根据朴素解法的过程一次性初始化单调集合,需要 $O(N^2\log N)$ 的平摊时间。因此,总时间复杂度为 $O(N^2\log N+QN\log N)$ 或 $O(N^2+QN\log N)$,具体取决于您所使用的集合是否支持在 $O(c+\log N)$ 时间内删除一段连续的 $c$ 个元素。

注意:为了避免使用双精度浮点数来比较两个分数 $\frac{a}{b}$ 和 $\frac{c}{d}$,我们可以比较 $a \cdot d$ 和 $b \cdot c$。

Joe's code:

#include <bits/stdc++.h>
using namespace std;

void fastIO() {
    ios_base::sync_with_stdio(false);
    cin.tie(0);
}

typedef long long ll;
#define ff first
#define ss second
int N, Q;
ll h[5100];
set<int> rig[5100]; // monotonic sets

bool comp(int ind, int i1, int i2) {
    // does index i2 to ind have a greater slope than index i1 to ind
    int d1 = abs(ind - i1), d2 = abs(ind - i2);
    ll h1 = h[i1] - h[ind], h2 = h[i2] - h[ind];
    return h2 * d1 >= h1 * d2;
}

int main() {
    fastIO();
    cin >> N;
    for (int i = 1; i <= N; i++) { cin >> h[i]; }
    for (int i = 1; i <= N; i++) {
        for (int j = i + 1; j <= N; j++) {
            if (rig[i].empty()) {
                rig[i].insert(j);
            } else {
                if (comp(i, *rig[i].rbegin(), j)) { rig[i].insert(j); }
            }
        }
    }
    int ans = 0;
    for (int i = 1; i <= N; i++) ans += (int)rig[i].size();
    cin >> Q;
    for (int i = 1; i <= Q; i++) {
        int x, y;
        cin >> x >> y; // update mountain x by incrementing it by height y
        h[x] += y;
        // update the sets to the left of x
        for (int j = 1; j <= x - 1; j++) {
            auto it = rig[j].lower_bound(x);
            bool add = false;
            if ((*it) == x) {
                add = true;
                it++;
            } else {
                --it;
                if (comp(j, (*it), x)) {
                    rig[j].insert(x);
                    ans++;
                    add = true;
                    it++;
                    it++;
                }
            }
            if (add) {
                while (it != rig[j].end() && !comp(j, x, (*it))) {
                    it = rig[j].erase(it);
                    ans--;
                }
            }
        }
        // update the set for x
        ans -= (int)rig[x].size();
        rig[x].clear();
        for (int j = x + 1; j <= N; j++) {
            if (rig[x].empty()) {
                rig[x].insert(j);
                ans++;
            } else {
                if (comp(x, *rig[x].rbegin(), j)) {
                    rig[x].insert(j);
                    ans++;
                }
            }
        }
        cout << ans << "n";
    }
}

Note: The above solution takes nearly 3s to run on some test cases. Depending on your language and implementation, you may have trouble passing all test cases even with the intended time complexity. In particular, a Java analog of the solution above using TreeSets took over 13s on some test cases (slower than the naive solution).

There are several ways to pass this problem without coming too close to the time limit:

  • Use vectors instead of sets (or ArrayLists instead of TreeSets in Java). The time complexity becomes $O(N^3+QN^2)$ or $O(QN^2)$ depending on your implementation, which is worse than the set solution, but we were unable to construct a test case where this solution took more than 2s to run, presumably because erasing from a vector has a good constant factor.
  • Use a segment tree instead of a set. The time complexity is the same as the set solution, but the constant factor is better. Our implementation runs in 0.8s.
  • Use a bitset instead of a set. The time complexity is $O(N^2+QN^2/B)$, where we assume standard operations on $B=64$-bit integers (for such an integer, checking whether it is nonzero, finding its first set bit, and finding its last set bit) take $O(1)$ time. The implementation below runs in 0.1s.

注意: 上面的解法在某些测试用例上运行时间接近3秒。根据你使用的语言和实现方式,即使时间复杂度是正确的,你也可能无法通过所有测试用例。特别是,对于使用TreeSets来实现上述解法的Java版本,在某些测试用例上需要13秒以上(比原始解法还慢)。

有几种方式可以通过这道题目,而不会接近时间限制:

  • 使用向量代替集合(或在Java中使用ArrayList代替TreeSet)。时间复杂度变成 $O(N^3+QN^2)$ 或 $O(QN^2)$,具体取决于你的实现方式。该复杂度比集合解法要差一些,但我们无法构造出一些需要超过2秒的测试用例,可能是因为擦除向量元素的常数因子比较好;
  • 使用线段树代替集合。时间复杂度与集合解法相同,但常数因子更好。我们的实现在0.8秒内运行;
  • 使用位集(bitset)代替集合。时间复杂度为 $O(N^2+QN^2/B)$,其中假设在$B=64$位整数上进行常规操作(对于这样一个整数,查看它是否非零、查找第一个设置位以及查找最后一个设置位所需的时间复杂度都是$O(1)$)。下面的实现可以在0.1秒内运行。

Ben's code:

#include <bits/stdc++.h>
using namespace std;

using ll = long long;

// Source: https://nyaannyaan.github.io/library/misc/bitset-find-prev.hpp.html
template <size_t Nb> struct Bitset : bitset<Nb> {
    template <typename... Args> Bitset(Args... args) : bitset<Nb>(args...) {}

    static constexpr int N = Nb;
    static constexpr int array_size = (Nb + 63) / 64;

    union raw_cast {
        array<uint64_t, array_size> a;
        Bitset b;
    };

    int _Find_prev(size_t i) const {
        if (i == 0) return -1;
        if ((*this)[--i] == true) return i;
        size_t M = i / 64;
        const auto &a = ((raw_cast *)(this))->a;
        uint64_t buf = a[M] & ((1ull << (i & 63)) - 1);
        if (buf != 0) return M * 64 + 63 - __builtin_clzll(buf);
        while (M--) {
            if (a[M] != 0) return M * 64 + 63 - __builtin_clzll(a[M]);
        }
        return -1;
    }

    inline int _Find_last() const { return _Find_prev(N); }
};

vector<ll> h;
bool comp(int ind, int i1, int i2) {
    return (i1 - ind) * (h[i2] - h[ind]) >= (i2 - ind) * (h[i1] - h[ind]);
}

Bitset<2000> segs[2000];
int N;

void build(Bitset<2000> &b, int st) {
    int lst = st;
    b.reset();
    for (int i = st + 1; i < N; ++i) {
        if (comp(st, lst, i)) {
            lst = i;
            b[i] = 1;
        }
    }
}

int main() {
    cin.tie(0)->sync_with_stdio(0);
    cin >> N;
    h.resize(N);
    for (auto &t : h) cin >> t;
    int ans = 0;
    for (int i = 0; i < N; ++i) {
        build(segs[i], i);
        ans += segs[i].count();
    }
    int Q;
    cin >> Q;
    while (Q--) {
        int x, y;
        cin >> x >> y;
        --x;
        h[x] += y;
        for (int j = 0; j < x; ++j) {
            int it = segs[j]._Find_next(x);
            int it_minus_one = segs[j]._Find_prev(it);
            assert(it_minus_one != -1);
            if (!comp(j, it_minus_one, x)) { continue; }
            if (!segs[j][x]) {
                segs[j][x] = 1;
                ++ans;
            }
            while (it < N) {
                if (comp(j, x, it)) break;
                int next_it = segs[j]._Find_next(it);
                segs[j][it] = 0;
                --ans;
                it = next_it;
            }
        }
        ans -= segs[x].count();
        build(segs[x], x);
        ans += segs[x].count();
        cout << ans << "n";
    }
}

Prob3

(Analysis by Benjamin Qi)

Solution 1:

We can reason as follows.

  • Let $v$ be some vertex of the graph with the minimum degree. If the optimal friendship group contains $v$, then the group is a subset of the connected component of $v$. Thus, such a friendship group can have strength at most $s$ equal to the degree of $v$ times the size of the connected component of $v$. The connected component of $v$ itself is a friendship group with strength $s$ as $v$ has minimum degree, so the highest strength of a friendship group containing $v$ is $s$.
  • If the optimal friendship group doesn't contain $v$, we can remove $v$ from the graph.

We can repeatedly identify the minimum degree vertex $v$ of the graph, update the answer to be at least $s$, and then remove $v$ in $O(M+N)$ time. However, computing connected components after every vertex removal naively takes $O(NM)$ time. We can speed this by reversing the sequence of vertex removals (so that we want to maintain connected components after adding instead of removing a vertex), and then using a Disjoint Set Union data structure. The time complexity is $O(M\alpha(N))$ (or $O(M\log M)$ if a set is used to identity and remove the minimum degree vertex).

题解 1:

我们可以如下推理。

  • 让 $v$ 为度数最小的顶点。如果最优的友谊团包含 $v$,则友谊团是 $v$ 所在的连通分量的子集。因此,这样的友谊团的强度 $s$ 最多是 $v$ 的度数乘以 $v$ 所在的连通分量的大小。$v$ 所在的连通分量本身的强度是 $s$,因为 $v$ 有最小度数,所以包含 $v$ 的友谊团的最高强度是 $s$。
  • 如果最优的友谊团不包含 $v$,我们可以从图中删除 $v$。

我们可以重复识别图的最小度数顶点 $v$,更新答案为至少 $s$,然后在 $O(M+N)$ 的时间内删除 $v$。但是,每次删除顶点后计算连通分量的时间复杂度为 $O(NM)$。我们可以通过反转顶点删除序列(从而希望在添加顶点而不是删除顶点后维护连通分量),然后使用树上启发式合并来加速这一过程。时间复杂度为 $O(M\alpha(N))$(如果使用集合识别和删除最小度数顶点,则时间复杂度为 $O(M\log M)$)。

Timothy Qian's code:

#include <bits/stdc++.h>

using namespace std;

struct DSU {
  vector<int> e;

  DSU(int n) { e = vector<int>(n, -1); }

  int get(int x) { return e[x] < 0 ? x : e[x] = get(e[x]); }

  bool same_set(int a, int b) { return get(a) == get(b); }

  int size(int x) { return -e[get(x)]; }

  bool unite(int x, int y) {
    x = get(x), y = get(y);
    if (x == y) return false;
    if (e[x] > e[y]) swap(x, y);
    e[x] += e[y];
    e[y] = x;
    return true;
  }
};

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
  int n, m;
  cin >> n >> m;
  vector<vector<int>> g(n);
  vector<int> deg(n);
  for (int i = 0; i < m; ++i) {
    int u, v;
    cin >> u >> v;
    --u, --v;
    g[u].push_back(v);
    g[v].push_back(u);
    ++deg[u];
    ++deg[v];
  }
  set<array<int, 2>> vertices;
  for (int i = 0; i < n; ++i) {
    vertices.insert({deg[i], i});
  }
  vector<int> order;
  vector<int> degrees;
  vector<bool> active(n, true);
  auto remove = [&]() {
    auto top = *vertices.begin();
    int u = top[1];
    int degree = top[0];
    order.push_back(u);
    degrees.push_back(degree);
    active[u] = false;
    for (int v : g[u]) {
      if (active[v]) {
        vertices.erase({deg[v], v});
        --deg[v];
        vertices.insert({deg[v], v});
      }
    }
    vertices.erase({deg[u], u});
  };
  for (int i = 0; i < n; ++i) {
    remove();
  }
  reverse(order.begin(), order.end());
  reverse(degrees.begin(), degrees.end());
  active.assign(n, false);
  DSU dsu(n);
  int mx = 1;
  long long ans = 0;
  for (int i = 0; i < n; ++i) {
    int u = order[i];
    active[u] = true;
    for (int v : g[u]) {
      if (active[v]) {
        dsu.unite(u, v);
        mx = max(mx, dsu.size(u));
      }
    }
    ans = max(ans, 1ll * mx * degrees[i]);
  }
  cout << ans << 'n';
  return 0;
}

Solution 2:

Suppose we are looking for the strongest friendship group where the cow with the minimum number of friends has exactly $d$ friends. We can find such a friendship group as follows: first, repeatedly remove any vertex with degree less than $d$ from the graph, and then return the largest connected component. We can do this in $O(M)$ time for each of $d=1,2,\dots$, and so on until the graph is empty. As a friendship group where every member has at least $d$ friends must contain at least $\frac{(d+1)d}{2}$ pairs of friendships, so once $\frac{(d+1)d}{2}>M$, the graph must be empty. Thus, this solution runs in $O(M\sqrt M)$ time.

The code solution uses DSU (which adds an extra factor of $\alpha(N)$), though this may be substituted with any other method of finding connected components (such as BFS or DFS).

题解 2:

假设我们正在寻找强度最高的友谊团,其中有恰好 $d$ 个朋友,即最小朋友数的奶牛恰好有 $d$ 个朋友。我们可以按照以下步骤找到这样的友谊团:首先,反复从图中删除任何度小于 $d$ 的顶点,然后返回最大的连通分量。我们可以在 $d=1,2,\dots$,以及直到图为空为止的情况下每次都以 $O(M)$ 的时间完成此操作。由于每个成员都至少有 $d$ 个朋友的友谊团必须包含至少 $\frac{(d+1)d}{2}$ 对友谊,因此一旦 $\frac{(d+1)d}{2}>M$,则图必须为空。因此,此算法的时间复杂度为 $O(M\sqrt M)$。

代码解决方案使用并查集(另加一个因素 $\alpha(N)$),但这可以替换为查找连通分量的任何其他方法(例如 BFS 或 DFS)。

Nick Wu's code:

#include <algorithm>
#include <cstdio>
#include <numeric>
#include <vector>

using namespace std;

struct disjoint_set {
  vector<int> p, sz;
  disjoint_set(int n) {
    p.assign(n, -1);
    sz.assign(n, 1);
  }
  int find(int x) {
    return p[x] < 0 ? x : (p[x] = find(p[x]));
  }
  int getsz(int x) {
    return sz[find(x)];
  }
  bool merge(int x, int y) {
    x = find(x);
    y = find(y);
    if(x == y) return false;
    p[x] = y;
    sz[y] += sz[x];
    return true;
  }
};

int main() {
  int n, m;
  scanf("%d%d", &n, &m);
  vector<vector<int>> edges(n);
  vector<int> edeg(n);
  for(int i = 0; i < m; i++) {
    int a, b;
    scanf("%d%d", &a, &b);
    a--; b--;
    edeg[a]++;
    edeg[b]++;
    edges[a].push_back(b);
    edges[b].push_back(a);
  }
  int ret = 0;
  vector<bool> deleted(n);
  vector<int> active(n);
  iota(active.begin(), active.end(), 0);
  for(int mindeg = 1; mindeg * mindeg <= m; mindeg++) {
    disjoint_set dsu(n);
    for(int i: active) {
      for(auto j: edges[i]) {
        if(!deleted[j] && dsu.merge(i, j)) ret = max(ret, dsu.getsz(i) * mindeg);
      }
    }
    vector<int> nactive;
    vector<int> q;
    for(int i: active) {
      if(edeg[i] == mindeg) {
        q.push_back(i);
      }
    }
    while(q.size()) {
      int i = q.back(); q.pop_back();
      if(deleted[i]) continue;
      deleted[i] = true;
      for(int j: edges[i]) {
        if(--edeg[j] <= mindeg) {
          q.push_back(j);
        }
      }
      edges[i].clear();
    }
    for(int i: active) if(edeg[i] > mindeg) nactive.push_back(i);
    active.swap(nactive);
  }
  printf("%dn", ret);
}

余姚中学2023年信息学夏令营报名通知

2023-05-25 21:23:04 By admin

信息科技是当今时代的发展趋势,互联网技术和人工智能等技术已深入我们生活的方方面面。而信息学能力是信息科技的核心竞争力。

余姚中学信息学团队历经20多年发展,屡创佳绩,已有逾200位同学获全国信息学奥林匹克联赛一等奖,7位同学代表浙江省参加全国信息学奥赛决赛并摘得金牌,20多位同学凭信息学竞赛获北大、清华等国内知名学府的保送或降分预录取资格,6人入选国家集训队,近七年内5人入选。2021年2月的全国信息学冬令营和7月的全国信息学奥林匹克决赛由中国计算机学会主办、余姚中学承办,这充分体现了余姚中学信息学团队,国内一流,省内领先的实力。NOI2022又有三位选手斩获银牌,获得“强基计划”破格入围资格。

为了进一步培养中小学生对信息学的兴趣,提升中小学生的信息素养,吸引并考察具有信息学拔尖创新潜质的学生,余姚中学决定于2023年6月17日,和余姚市教育局教研室联合举办2023年“余姚市信息学夏令营”。

一、报名资格

  1. 有“余姚中学小初高一体化培养”计划推荐的第一期、第二期优秀选手。
  2. 综合素质全面,学业成绩优秀,在信息学方面具有学科特长及创新潜质的优秀余姚在读小学生三年级以上;初一初二学生需要获得csp-j/s二等奖以上。

二、报名申请

由各校指导老师推荐。推荐表发送至邮箱:248675160@qq.com

报名起止时间:即日起-6月11日晚24:00。6月14日向各指导老师确认营员名单。

三、选拔程序

  1. 初审:专家组将对在规定期限内提交报名申请且内容和形式符合要求的申请材料进行评审,确定通过初审的参营名单。
  2. 测试:上机测试。
  3. 评奖:根据营员上机考试的成绩择优评定优异和优秀营员。特别优秀的获奖营员将有机会共同参与余姚中学信息学竞赛集训。

四、日程安排

6月17日上午8:30~11:00,直接上机考试。

五、附则

  1. 为保障大家的健康和安全,若有身体不适请及时就医,切勿参加活动
  2. 本次“余姚市信息学体验营”不收取参营费用,交通费用自理。

六、联系方式

报名咨询:诸老师13386632923

2023年5月22日
共 8 篇博客