Codeforces Round 1006 (Div. 3) 比赛记录

Codeforces Round 1006 (Div. 3) 比赛记录

比赛链接
这场的题目名称都很长啊~。
很简单的一场(毕竟是div3,能不简单嘛)赛时切掉了A - F,C题花的时间有点多,G题偶遇数学压轴拼尽全力无法战胜。

A. New World, New Me, New Array

由于取值范围是 pp,所以很明显 k 的正负是无所谓的,所以只需要判断最大能取到的值能否大于等于 |k|,然后用 |k| 除以 p 向下取整即可。

void solve()
{
    int n, k, p;cin >> n >> k >> p;

    k = abs(k);
    int mx = n * p;

    if(k == 0) {
        cout << 0 << '\n';
        return;
    }

    if(k > mx) {
         cout << -1 << '\n';
         return;
    } else {
         cout << (k - 1) / p + 1 << '\n';
    }
}

B. Having Been a Treasurer in the Past, I Help Goblins Deceive

组合数学问题,两个 - 和一个 _ 来组成一个表情,很明显,要最多的话,我们把所有的 _ 放在一起是最有的,因为此时他们可以共享所有的 -,然后再枚举一下左右两边 - 的数量乘一下取最大即可。

void solve()
{
    int n;cin >> n;
    string s;cin >> s;

    int cnt = 0;
    int ck = 0;
    for(auto &i : s) {
        cnt += (i == '-');
        ck += (i == '_');
    }

    int ans = 0;
    for(int i = 1;i <= cnt;i ++) {
        ans = max(ans, i * (cnt - i) * ck);
    }

    cout << ans << '\n';
}

C. Creating Keys for StORages Has Become My Main Skill

题目要我们构造出来的 MEX 值仅可能大,那就直接从 0 开始枚举呗,检验一下这个数字能否加入,能加入就加入,不能加入就停止循环,然后检验一下此时的前缀或和是否为 x,如果不是,那么就把下一个数设置为 x(如果位置不够用了,就把当前最后一个设置为 x),然后继续往后填 0
赛后在群里看到了其他大佬更优雅的做法:先把 0n1 全部加入,然后逐个检测是否合法,不合法就换为 x,然后再前缀或和算一下,如果不是 x 就把最后一个换为 x

//其实这个check可以直接用 x | i == i来替换,赛时铸币了
bool check(int x, int y) {
    for(int i = 0;i <= 31;i ++) {
        if(!(x & (1ll << i)) && (y & (1ll << i))) {
            return false;
        }
    }
    return true;
}

void solve()
{
    int n, x;cin >> n >> x;

    int ix = 0;

    for(int i = 0;ix < n;i ++) {
        if(check(x, i)) {
            a[++ ix] = i;
        } else break;
    }

    int pre = 0;
    for(int i = 1;i <= ix;i ++) {
        pre |= a[i];
    }

    if(pre != x) {
        if(ix == n)a[ix] = x;
        else a[++ ix] = x;
    }

    while(ix <= n) {
        a[++ ix] = 0;
    }

    for(int i = 1;i <= n;i ++) {
        cout << a[i] << " \n"[i == n];
    }

}

D. For Wizards, the Exam Is Easy, but I Couldn't Handle It

选择一个子区间,把区间头移动到尾部,使得改动后逆序对尽可能少。
看到逆序对,首先想到了树状数组,然后一看数据范围:1000?!
直接 n2 暴力启动。
枚举每一个点 i 作为起点,往后搜索 j,如果 aj>ai,移动后会让逆序对增加 1,如果 aj<ai,移动后会让逆序对减少 1,时时更新当前逆序对的减少数,一旦减得更多了,就更新答案区间。

void solve()
{
    int n;cin >> n;
    for(int i = 1;i <= n;i ++)cin >> a[i];

    pair<int, int> ans = {1, 1};
    int mx = 0;
    int sum[2] = {0};
    for(int i = 1;i <= n;i ++) {
        sum[1] = 0;
        sum[0] = 0;

        for(int j = i + 1;j <= n;j ++) {
            if(a[j] < a[i])sum[1] ++;
            if(a[j] > a[i])sum[0] ++;
            int now = sum[1] - sum[0];
            if(now > mx) {
                ans = {i, j};
                mx = now;
            }
        }
    }

    cout << ans.first << ' ' << ans.second << '\n';
}

E. Do You Love Your Hero and His Two-Hit Multi-Target Attacks?

这个题可以用类似倍增的想法来做。
如果一堆点 x 坐标相同,那么这一堆点里面可以选出来的对数是 C(n,2)
那么我们可以先预处理 1500C(n,2) 的值,然后每次挑尽可能大的满足小于等于当前剩余值的 n来连成一条线,这个可以二分实现。
为了节约,我们可以一条竖着一条横着这么放,这样每次都可以省下来一个(但其实是不需要的,只需要每次结束时 xy 都加一就能和上一条断开了,数量也是足够的,并不需要省)。

void init() {
    for(int i = 1;i <= 500;i ++) {
        a[i] = i * (i - 1) / 2;
    }
}

void solve()
{
    int k;cin >> k;
    int ix = upper_bound(a + 1, a + 500 + 1, k) - a;

    int sum = k;
    int cnt = 0;
    int x = 0;
    int y = 0;
    
    vector<pair<int, int> > ans;
    ans.push_back({0, 0});

    while(sum) {
        cnt ++;
        int ix = upper_bound(a + 1, a + 500 + 1, sum) - a - 1;
        sum -= a[ix];
        if(cnt & 1) {
            x ++;
            for(int i = 1;i < ix;i ++) {
                ans.push_back({x, y});
                x ++;
            }
            x --;
        } else {
            y ++;
            for(int i = 1;i < ix;i ++) {
                ans.push_back({x, y});
                y ++;
            }
            y --;
        }
    }

    cout << ans.size() << '\n';
    for(auto &[u, v ] : ans) {
        cout << u << ' ' << v << '\n';
    }
}

F. Goodbye, Banker Life

手玩几个样例后发现和 k 根本没有任何关系。
打表可以发现,如果 x 是偶数的话,这一行的构造就相当于 x/2 行的构造每个数字相邻出现两次。
因此选择递归实现:

  • 如果 x 是偶数,那就往 x/2 递归,传递时把每个值相邻堆叠两次。
  • 如果 x 是奇数,那么 x1 就是偶数,向 x1 递归,然后按题目规则算 x 行即可。

但实际上这个做法是复杂了的,因为其实每一个位置异或上 1 的次数,就是这个位置的杨辉三角值,题目的传递方式也是按杨辉三角的规则进行传递的。
所以只需要计算这个位置的组合数是奇数还是偶数,就知道这个位置是 0 还是 1 了。
最后全部乘上 k 就行。
(不过我觉得递归做法也还是蛮巧妙的)

void get(int x) {
    if(x == 1) {
        a[1] = 1;
        return;
    }

    if(x & 1) {
        get(x - 1);
        for(int i = 1;i <= x;i ++) {
            if(i == 1) {
                tmp[i] = a[i];
            } else if(i == x) {
                tmp[i] = a[1];
            } else {
                tmp[i] = a[i - 1] ^ a[i];
            }
        }
        for(int i = 1;i <= x;i ++) {
            a[i] = tmp[i];
        }
    } else {
        get(x / 2);
        for(int i = 1;i <= x / 2;i ++) {
            tmp[i * 2] = tmp[i * 2 - 1] = a[i];
        }
        for(int i = 1;i <= x;i ++) {
            a[i] = tmp[i];
        }
    } 
}

void solve()
{
    int n, k;cin >> n >> k;
    get(n);

    for(int i = 1;i <= n;i ++) {
        cout << a[i] * k << " \n"[i == n];
    }
}

离 AK 最近的一次,希望有朝一日能 AK div3!

posted @   天天超方的  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
点击右上角即可分享
微信分享提示