AtCoder Beginner Contest 354 A~F

A.Exponential Plant(模拟)

题意

有一棵植物,从第 0 0 0天起每天结束后会长高 2 i 2^{i} 2i厘米,问:第几天开始时植物的高度会超过给出的身高。

分析

循环累加直到超过身高即可。

Hint:注意当给出身高较大时,植物高度可能在累加中超过int范围,需要使用long long类型存储。

代码

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

const int N = 3e5 + 5e2;

int n, m, k, a[N];

void solve() {
    cin >> n;
    ll h = 0;
    for (int i = 0; ; i++) {
        h += (1ll << i);
        if (h > n) {
            cout << i + 1 << endl;
            return;
        }
    }
}

int main () {
    solve();
    return 0;
}

B.AtCoder Janken 2(排序)

题意

N N N个人参加了一场比赛,每个人有着自己的名字以及获得的分数。

你需要按照以下规则计算出最后的胜利者:

  • 将这 N N N个人按照名字的字典序进行排序

  • 计算这 N N N个人的分数总和 T T T,并让 T T T N N N取模,让 T T T% N N N作为下标,排序好的所有人中下标为 T T T% N N N的即为最后的胜利者

分析

由于分数只是用来计算最后的胜利者,因此可以不用和名字一起放在结构体中,可以单独存储。

字符串排序默认就是按字典序排序的,因此可以直接对字符串数组进行 s o r t sort sort

统计完分数总和以及将名字排序后,以分数总和取模后的结果作为下标输出胜利者的名字即可。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 5e2;

int n, m, k, a[N];
string s[N];

void solve() {
    cin >> n;
    int sum = 0;
    for (int i = 1; i <= n; i++) {
        cin >> s[i] >> a[i];
        sum += a[i];
    }
    sort(s + 1, s + n + 1);
    cout << s[sum % n + 1] << endl;
}

int main () {
    solve();
    return 0;
}

C.AtCoder Magics(双指针+multiset)

题意

给出 N N N张卡牌,每张卡牌均包含两个属性:力量 A i A_i Ai以及花费 C i C_i Ci

你可以执行以下操作若干次直到无法进行操作:

  • 选择两张卡牌 x , y x, y x,y,且满足 A x > A y A_x > A_y Ax>Ay and C x < C y C_x < C_y Cx<Cy,将卡牌 y y y丢掉。

问:最后留下来了哪些卡牌,输出这些卡片的编号。

分析

首先可以对卡牌按 A i A_i Ai从小到大排序,那么排完序后,只要有 i < j i < j i<j,必有 A i ≤ A j A_i \le A_j AiAj,即所有能淘汰当前卡牌的牌下标必须大于当前卡牌,可以通过upper_bound查找第一个大于等于 A i + 1 A_{i} + 1 Ai+1的卡牌出现的位置。

但是,可以发现,虽然后面的卡牌的力量均大于等于当前卡牌,但是此时所有卡牌的花费依然是无序的,没有办法解决花费的问题。

这里可以使用 m u l t i s e t multiset multiset(不去重的 s e t set set)维护所有力量大于当前卡牌的牌的花费,每次遍历到一张卡牌时,使用另一个指针将所有力量与当前的卡牌相同的牌的花费从set中删除。

这样, s e t set set中所有元素对应卡牌的力量都是比当前卡牌更大的,那么只要存在其中一张的花费比当前卡牌小,当前卡牌就会被删除,即,只有当前卡牌的花费比后面所有牌的花费均小(小于等于),当前卡牌就不会被删除,由于 m u l t i s e t multiset multiset也会排序,那么只需要与 m u l t i s e t multiset multiset存储的第一个元素比较即可,如果当前卡牌的花费小于等于存储的最小的花费,那么当前卡牌就会被保留。

由于需要排序,那么会打乱卡牌的编号,可以使用结构体一起记录卡牌的编号,卡牌将被删除仅使用vector记录卡牌编号,最后将编号排序输出即可。

hint

如果掌握线段树等解决区间极值的算法,不难想到,可以对排序后卡牌的花费维护区间最小值,每次使用二分找到第一个力量大于当前卡牌的卡牌所在的下标,然后对这个下标到 n n n查询区间最小值,如果查询到的结果要大于等于当前卡牌的花费,则当前卡牌就不会被淘汰。

代码

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

const int N = 3e5 + 5e2;

struct Node{
    int a, c, id;
    bool operator < (const Node &o) const {
            return a < o.a;
    }
}a[N];

int n;
multiset<int> st;
vector<int> ans;

void solve() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i].a >> a[i].c;
        a[i].id = i;
        st.insert(a[i].c);
    }
    sort(a + 1, a + n + 1);
    int j = 1;
    for (int i = 1; i <= n; i++) {
        while (j <= n && a[i].a == a[j].a) {
            st.erase(a[j++].c);
        }
        if (st.empty() || *st.begin() >= a[i].c) {
            ans.push_back(a[i].id);
        }
    }
    sort(ans.begin(), ans.end());
    cout << ans.size() << endl;
    for (auto i : ans) cout << i << ' ';
}

int main () {
    solve();
    return 0;
}

D.AtCoder Wallpaper(思维)

题意

给出一个二维坐标系,二维坐标系内的元素如下图所示:

给出两个坐标 ( A , B ) , ( C , D ) (A, B), (C, D) (A,B),(C,D),问以 ( A , B ) (A, B) (A,B)为左下角 ( C , D ) (C, D) (C,D)为右上角的矩形中,包含多少个黑色的三角形?

分析

如下图红框内所示,实际上图形是以一个 2 × 4 2 \times 4 2×4的矩形循环摆放的:

因此,对于矩形内的黑色三角形数量,可以将问题转化为区间内, 2 × 4 2 \times 4 2×4的矩形中每一个小网格的出现次数,再乘上这个小网格包含的黑色三角形数量,就是这个小网格产生的贡献。

每一个小网格的出现次数可以通过横坐标上出现次数乘上列坐标上出现次数计算得到。

代码

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

const int N = 3e5 + 5e2;

ll p[2][4] = {{2, 1, 0, 1},
              {1, 2, 1, 0}};

const ll maxn = 1e9 + 4;//随意取一个大整数,需要保证足够大且是4的倍数

void solve() {
    ll a, b, c, d;
    cin >> a >> b >> c >> d;
    ll ans = 0;
    for (int y = 0; y < 2; y++) {
        for (int x = 0; x < 4; x++) {
            ll sum_x = (c - x + 3 + maxn) / 4 - (a - x + 3 + maxn) / 4,
               sum_y = (d - y + 1 + maxn) / 2 - (b - y + 1 + maxn) / 2;
            ans += sum_x * sum_y * p[y][x];
        }
    }
    cout << ans << endl;
}

int main() {
    solve();
    return 0;
}

E.Remove Pairs(dp)

题意:

A A A和小 B B B正在玩一个使用 N N N 张卡片的游戏。 第 i i i张牌的正面写着 A i A_i Ai ,背面写着 B i B_i Bi 。最初, N N N 这张牌摆在桌子上。小 A A A先出,两位玩家轮流进行以下操作:

  • 从桌上选择一对正面数字相同或背面数字相同的牌,然后从桌上拿走这两张牌。如果没有这样的一对牌,玩家就不能进行操作。

最先无法进行操作的玩家输,另一名玩家赢。如果双方都以最佳方式出牌,谁会赢?

分析:

d p [ i ] dp[i] dp[i]表示用一个二进制数来代表现在还有的卡牌状况。先手是必胜还是必输。当前转移是必胜还是必输,要看后继状态是否存在必输态。如果存在必输态,则当前状态 i i i可以通过对应的转移变成先手必输态 j j j,那么说明当前状态 i i i是必胜, d p [ i ] = 1 dp[i]=1 dp[i]=1,否则如果所有后继状态都是必胜态,则说明当前是必输态, d p [ i ] = 0 dp[i]=0 dp[i]=0。而后继状态就是当前状态可以做的决策的转移,即选择两张正面数字一样或反面数字一样的卡牌拿走。我们可以 n 2 n^2 n2枚举选择哪两张牌即可。

代码:

#include <bits/stdc++.h>

using namespace std;
using namespace std;

int main() {
    int n;
    cin >> n;
    vector<array<int, 2>> tmp(n);
    for (auto &v: tmp)
        cin >> v[0] >> v[1];
    int num = (1 << n);
    vector<int> dp(num, -1);
    dp[0] = 0;
    auto dfs = [&](auto dfs, int s) -> int {
        if (dp[s] != -1)
            return dp[s];
        int flag = 0;
        for (int i = 0; i < n; i++) {
            if ((~s >> i) & 1)
                continue;
            for (int j = i + 1; j < n; j++) {
                if ((~s >> j) & 1)
                    continue;
                if (tmp[i][0] != tmp[j][0] && tmp[i][1] != tmp[j][1])
                    continue;
                flag |= !dfs(dfs, s ^ (1 << i) ^ (1 << j));
            }
        }
        return dp[s] = flag;
    };
    int flag = dfs(dfs, num - 1);
    if (flag) {
        cout << "Takahashi" << endl;
    } else {
        cout << "Aoki" << endl;
    }
    return 0;
}

F.Useless for LIS(树状数组)

题意:

问题陈述

给你一个长度为 N N N的整数序列 A A A

对于每个 t = 1 , 2 , … , N t=1,2,\dots,N t=1,2,,N,判断 A t A_t At是否包含在 A A A的最长递增子序列中。

这里,只有当且仅当下面的条件成立时, A t A_t At才包含在 A A A的最长递增子序列中:

  • L L L A A A的最长递增子序列的长度。存在一个严格递增整数序列 i = ( i 1 , i 2 , … , i L ) ( i 1 < i 2 < ⋯ < i L ) i=(i_1,i_2,\dots,i_L)(i_1\lt i_2\lt \dots\lt i_L) i=(i1,i2,,iL)(i1<i2<<iL),其中每个元素都介于 1 1 1 N N N之间(包括首尾两个元素),且满足以下所有条件:

    • A i 1 < A i 2 < ⋯ < A i L A_{i_1}\lt A_{i_2}\lt\dots\lt A_{i_L} Ai1<Ai2<<AiL
    • i k = t i_k=t ik=t为某个 k ( 1 ≤ k ≤ L ) k(1\leq k\leq L) k(1kL)

给你 T T T个测试样例,逐个求解。

什么是最长递增子序列?

序列 A A A的子序列是指从 A A A中提取一些元素而不改变顺序所得到的序列。

序列 A A A的最长递增子序列是 A A A的子序列,它以最大可能的长度严格递增。

分析:

用树状数组求最长上升子序列长度。

本题 a i a_i ai比较大,上树之前先进行离散化处理。

定义 d i d_i di为以 a i a_i ai结尾的最长上升子序列的长度。

根据树状数组对最长上升子序列长度的计算方法可以得出一个结论:

  • 对于所有满足 d x ≥ 2 d_x \ge 2 dx2的整数 x x x,必然存在整数
    y y y满足 x < y x\lt y x<y d y + 1 = d x d_y+1=d_x dy+1=dx

也就是说,我们可以从后往前枚举 y y y,寻找符合条件的 x x x,若 x x x存在,这个下标就是答案之一。

代码:

#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL mod=1000000007;
const int N=2e5+10;
int n, a[N], b[N], m, tr[N], dp[N], req[N], res;
list<int> ret;
inline void update(int x, int v){
    for (; x <= m; x += (x & -x))
        tr[x] = max(tr[x], v);
}
inline int query(int x){
    int res = 0;
    for (; x; x -= (x & -x))
        res = max(res, tr[x]);
    return res;
}
void solve(){
    cin >> n;
    for (int i = 1; i <= n; i++){
        cin >> a[i];
        b[i] = a[i];
    }
    sort(b + 1, b + n + 1);
    m = unique(b + 1, b + n + 1) - b - 1;
    memset(tr + 1, 0, m << 2);
    memset(req + 1, 0, m << 2);
    for (int i = 1; i <= n; i++){
        a[i] = lower_bound(b + 1, b + m + 1, a[i]) - b;
    }
    res = 0;
    for (int i = 1; i <= n; i++){
        dp[i] = query(a[i] - 1) + 1;
        res = max(res, dp[i]);
        update(a[i], dp[i]);
    }
    ret.clear();
    req[res] = 0x3f3f3f3f;
    for (int i = n; i; i--){
        if (req[dp[i]] > a[i])
            ret.push_front(i), req[dp[i] - 1] = max(req[dp[i] - 1], a[i]);
    }
    cout << ret.size() << endl;
    list<int>::iterator it;
    for (it = ret.begin(); it != ret.end(); ++it) {
        cout << *it << ' ';
    }
    cout << endl;
}
int main(){
    int T;
    cin >> T;
    while (T--)
        solve();
    return 0;
}

赛后交流

在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。

群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

  • 24
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值