官方题解|ACGO挑战赛#1
2024-03-11 14:31:51
发布于:浙江
直播预告
直播时间:3月2日(周六)下午4点-6点
直播地址: B站ACGO官方账号
直播内容:挑战赛#1复盘(欢迎观看大型翻车现场)
主讲人:小鱼老师
T1 - 算术排列
分析题目不难发现,大的数字能变小,小的数字不能变大,容易想到一个贪心的思路:
令当前数字为 :
- 若 ,将 整除 。
 - 若已经有数字 ,将 整除 2。
 
重复以上操作直至 变为 或者找到一个没有得到的排列中的数字为止。
使用一个数组  存储  到  一共  个数字是否出现过。
若操作完整个数组  后,以上条件满足则输出  否则输出 。
AC代码
#include <bits/stdc++.h>
using namespace std;
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    int _; cin >> _;
    while (_--) {
        int n; cin >> n;
        vector<int> p(n + 1);
        for (int i = 0; i < n; ++i) {
            int x; cin >> x;
            while (x > 1 and (x > n or p[x]))
                x /= 2;
            p[x] = 1;
        }
        int cnt = 0;
        for (int i = 1; i <= n; ++i)
            if (p[i] == 1) ++cnt;
        cout << (cnt == n ? "YES" : "NO") << '\n';
    }
    return 0;
}
复杂度分析
对于数组中的每个数字,将其做处理的时间复杂度为 ,处理完整个数组时间复杂度为 )。
T2 - 数组片段匹配
注意题目中标注要求满足条件的长度相同的子数组中,只需要 数对 其中 即可。
这点提示我们要关注相同的元素。那么对于两个相同的元素,各自形成的子数组,如果两者在原数组中距离较远,则包含其子数组长度越短,反之,包含其子数组的长度越长。
在原数组中令 这个长度可以拆分成两个部分 和 ,其中 ,那么最终我们要求的答案就是枚举数组中相邻的相同数字的位置 即 最大的就是答案。
若数组中没有重复数字,显然不存在满足条件的两个子片段。
AC代码
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 150000;
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    int _; cin >> _;
    while (_--) {
        int n; cin >> n;
        int pre[N + 1], max_len = -1;
        memset(pre, -1, sizeof pre);
        for (int i = 0; i < n; ++i) {
            int x; cin >> x;
            if (pre[x] != -1)
                max_len = max(n - (i - pre[x]), max_len);
            pre[x] = i;
        }
        cout << max_len << '\n';
    }
    return 0;
}
复杂度分析
代码实现上,遍历数组 ,令外使用一个数组 记录数字 在数组中出现的上一个位置,随着遍历不断更新,得到答案。时间复杂度为 。
T3 - 勇敢的冒险者
题目大意
冒险者在一条 轴上,并处于点 ,假设当前的位置是 ,现在正在进行第 次跳跃,有两种跳跃的方案
- 向前跳跃到
 - 向后跳跃到
 
冒险者要到达点 点
题意分析
问最少跳跃几次,冒险者就能到达 点
解题思路
由于一开始冒险者一定是在点 的,并且 点一定是大于 的,很容易看出一开始只要一直执行 【方案一】 即 就一定能到达 的位置。
如果执行了 次后正好能到达 点那么答案为
那如果超过了 点呢? 这个时候我们需要考虑用 【方案二】 来进行干预了。
接下来我们分类讨论一下:
- 
进行次操作后,到达了 的位置,那么我们只需要执行一次 【方案二】
 - 
进行次操作后,到达了 的位置,其中 为超出 的部分
这个时候可以有两种办法消除这个 。
第一种选择执行 次 【方案二】
第二种选择在之前跳跃的某次 【方案一】 将它改为 【方案二】
显然这里选择 【方案二】 是最优的
 
所以我们只需要关心,经历几次 能跳到 的点上就行了,这里可以使用二分去快速找到
时间复杂度解析
对于每个 都需要进行一次二分即复杂度为:
代码演示
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll getSum(ll r) {
    return (1 + r) * r / 2;
}
int main() {
    int t,x;
    cin >> t;
    while(t--) {
        cin >> x;
        int l = 0,r = x;
        while(l <= r) {
            int mid = (l + r) >> 1;
            if (getSum(mid) >= x)r = mid - 1;
            else l = mid + 1;
        }
        ll s = getSum(l);
        if (s == x) {
            cout << l << endl;
        }else {
            if (s - 1 == x) {
                cout << l + 1 << endl;
            }else {
                cout << l << endl;
            }
        }
    }
    return 0;
}
T4 - 超能战士
题目大意
有 只怪兽,每只怪兽都有生命值 和攻击力 ,超能战士每次可以对所有活着的怪兽造成 点伤害。
怪兽会在每次超能战士攻击后进行反击,活着最弱的怪兽会降低超能战士攻击伤害,降为
题意分析
求问超能战士能不能消灭所有的怪兽
解题思路
从题中可以发现,超能战士的攻击力 同时可以看做是超能战士的血量,当 的时候,如果场上还有怪兽,则不能消灭所有怪兽。
超能战士每进行一次攻击可以消灭所有当前血量 的怪兽,我们可以模拟这个过程直到超能战士的攻击力 ,所以重点考虑怎么去模拟这个过程,如果用暴力的方式,一遍一遍让怪兽的血量减去 ,那么复杂度为 ,超时!
实际上我们只需要找到第一个不能被杀死的怪兽,即第一个血量 的怪兽。
我们可以先按照血量从小到大排序,我们记找到第一个血量 怪兽的下标为 ,那么 的怪兽都视为死亡,的怪兽都为存活。由于血量是有序的了,满足单调性,我们可以使用二分。由于怪兽的血量是会变化的,在二分的时候要将怪兽的血量减去超能战士累积攻击的伤害。
我们还有个问题没有解决,如何在存活的怪兽中,找到最弱的,即攻击力最小的。存活的下标区间为 ,我们可以贪心最小值,倒着遍历不断看右边的最小值与当前值哪一个更小,来维护一个最小值数组。
时间复杂度解析
维护小最值数组,需要遍历所有怪物,复杂度为
查找第一个不能杀死的怪物,用到了二分,直到 时,结束模拟过程。复杂度为
代码演示
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int t,n,k;
int s = 0;
struct w{
    int h,p;
}arr[N];
int zx[N];
bool cmp(const w &a,const w &b) {
    return a.h < b.h;
}
int fd(int l,int r,int target) {
    while(l <= r) {
        int mid = (l + r) >> 1;
        if (arr[mid].h - s > target)r = mid - 1;
        else l = mid + 1;
    }
    return l >= n ? -1 : l;
}
int main() {
    ios::sync_with_stdio(false);
    cin >> t;
    while(t--) {
        s = 0;
        cin >> n >> k;
        memset(zx,0,sizeof zx);
        for(int i=0;i<n;i++) cin >> arr[i].h;
        for(int i=0;i<n;i++) cin >> arr[i].p;
        sort(arr,arr+n,cmp);
        int l = 0,r = n - 1;
        zx[r] = arr[r].p;
        for(int i=r-1;i>=0;i--) {
            zx[i] = min(zx[i+1],arr[i].p);
        }
        while(k > 0) {
            int idx = fd(l,r,k);
            if (idx != -1) {
                l = idx;
            }
            int p = zx[idx];
            s += k;
            if (s >= arr[r].h)break;
            k -= p;
        }
        if (s >= arr[r].h) {
            cout << "YES" << endl;
        } else {
            cout << "NO" << endl;
        }
    }
    return 0;
}
T5 - 迷宫L
题目大意
有一个 和 组成的矩阵,有一个操作,选择一个 的子矩阵,在这个子矩阵中选择一个 ,把这个 中的三个数全部变成 ,如果选择的这个 中至少包含一个 的话,则可以操作。
题意分析
问最多可以操作多少次
解题思路
如果要让次数最多,那我们要尽可能用上每一个 ,换句话说最好一次只消除一个 。我们可以发现当一个 的子矩阵中,存在两个 的情况下,可以一次只消除一个
那么如果没有出现以上这种情况,我们可以先进行一次操作,这样必定能构造出来在 中出现两个 的情况。这个时候分类讨论一下,我们记 出现的次数为
- 如果子矩阵中出现了一个 则答案为
 - 如果一个 都没有的情况,答案为
 
时间复杂度解析
我们需要遍历一次矩阵,复杂度为:
代码演示
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
int t,n,m;
char x;
int on,zn;
int mp[N][N];
int main() {
    cin >> t;
    while(t--) {
        on = zn = 0;
        cin >> n >> m;
        memset(mp,0,sizeof mp);
        for(int i=1;i<=n;i++) {
            for(int j=1;j<=m;j++) {
                cin >> x;
                mp[i][j] = x - '0';
            }
        }
        int flag = 0;
        for(int i=1;i<=n;i++) {
            for(int j=1;j<=m;j++) {
                if (mp[i][j])on++;
                else zn++;
                if (i+1 <= n && j+1 <=m) {
                    int cnt = 4 - (mp[i][j] + mp[i+1][j] + mp[i][j+1] + mp[i+1][j+1]);
                    if (cnt >= 2) {
                        flag = 1;
                    }
                }
            }
        }
        if (flag) {
            cout << on << endl;
        } else {
            if (zn) {
                cout << on - 1 << endl;
            } else {
                cout << on - 2 << endl;
            }
        }
    }
    return 0;
}
T6 - 质数花瓣
题目大意
在一个长度为 的数字区间内,寻找符合要求的质数。
题意分析
如果每次去掉数字的最后一位,仍是质数,则满足要求
解题思路
素数筛模板题,可以套用埃氏筛或欧拉筛
如果你的埃氏筛超时了,请将标记数组换成bitset容器
如果你的欧拉筛超内存了,请将数据类型换成short
当然此题不止这一种解法,你还可以按数字位搜索,找到符合条件的数。
时间复杂度
时间复杂度为:
代码演示
#include <bits/stdc++.h>
using namespace std;
const int N = 1e8 + 10;
bitset<N> st;
void get(int r) {
    st[1] = 1;
    for(int i=2;i<=r/i;i++) {
        if (st[i])continue;
        for(int j=i*i;j<=r;j+=i) {
            st[j] = 1;
        }
    }
}
int main() {
    int n;
    cin >> n;
    int l = pow(10,n-1),r = pow(10,n);
    get(r);
    for(int i=l;i<r;i++) {
       int f = 1;
       int x = i;
       while(x) {
           if (st[x]) {
               f = 0;
               break;
           }
           x /= 10;
       }
       if (f)cout << i << endl;
    }
    return 0;
}
全部评论 1
T6不是深搜吗 快哉快哉
2024-11-22 来自 广东
0














有帮助,赞一个