CodeForces Round 905 div3 A~G

CodeForces Round 905 div3 A~G

A. Morning

​ 题意:给你一个长度为4的仅有小写字母子组成的字符串,让你从一个字符集 “1234567890” 中且初始位置为 ‘1’ 的字符开始,每次操作可以切换至相邻的字符或确定当前字符(不能从 ‘1’ 切换至 ‘0’,反之亦然),问得到每个字符串需要花费最少多少次操作。

​ 思路:暴力模拟即可,注意 ‘0’ 应该在最后一位,即应代表10。

void solve()
{
    string s;
    cin >> s;
    int ans = 0;
    char ch = '1';
    for (int i = 0; i < 4; i++)
    {
        ans += abs((s[i] == '0' ? ('9' + 1) : s[i]) - ch) + 1;
        ch = s[i] == '0' ? '9' + 1 : s[i];
    }
    cout << ans << endl;
}

int main()
{
    IOS;
    int t = 1;
    cin >> t;
    while (t--)
        solve();

    return 0;
}

B. Chemistry

​ 题意:给你一个 k,问能否在字符串中仅删去 k 个字符后使字符串为一个回文串。

​ 思路:模拟 + 贪心。易知回文串中最多允许存在一个数量为奇数的字符,所以可以将字符串中每个字符出现的次数统计下来,先用 k 减去每个出现次数为奇数的字符一次,使其出现次数为偶数,再判断剩下的几种情况:

​ 1、k 为 0,若奇数个字符的数量大于 1,输出 NO,否则 YES;

​ 2、k 不为 0,此时肯定不存在奇数个字符,所以只需要判断剩下字符的总数量是否大于等于 k 即可。

void solve()
{
    int n, k;
    cin >> n >> k;
    string s;
    cin >> s;
    map mp;
    for (char ch : s)
        mp[ch]++;
    for (auto &p : mp)
    {
        if (k > 0 and p.sc % 2 == 1)
        {
            p.sc--;
            k--;
        }
    }
    int f = false;
    for (auto &p : mp)
    {
        if (p.sc % 2 == 1)
        {
            if (not f)
                f = true;
            else
            {
                cout << "NO" << endl;
                return;
            }
        }
    }
    int sum = 0;
    for (auto &p : mp)
        sum += p.sc;
    if (sum >= k)
        cout << "YES" << endl;
    else
        cout << "NO" << endl;
}
 
int main()
{
    IOS;
    int t = 1;
    cin >> t;
    while (t--)
        solve();
 
    return 0;
}

C. Raspberries

​ 题意:给你一个长度为 n 的数组和一个整数 k,每次操作你可以选择数组中的一个数,使其加一。问你至少需要进行多少次操作使这个数组所有元素的乘积可以被 k 整除?

​ 思路:模拟。注意到 k 的范围只有 [2, 5],所以我们可以枚举 k 的情况并讨论。具体讨论可以看代码(4 的情况是真的多。。)

#define REP(i, n) for (int i = 0; i < n; i++)
using vi = vector;

void solve()
{
    int n, k;
    cin >> n >> k;
    vi a(n);
    REP(i, n) cin >> a[i];
    if (k == 2)
    {
        REP(i, n)
        if (a[i] % 2 == 0)
        {
            cout << 0 << endl;
            return;
        }
        cout << 1 << endl;
    }
    else if (k == 3)
    {
        bool f1 = false, f2 = false;
        REP(i, n)
        {
            if (a[i] % 3 == 0)
            {
                cout << 0 << endl;
                return;
            }
            else if (a[i] % 3 == 2)
                f2 = true;
            else
                f1 = true;
        }
        cout << (f2 ? 1 : 2) << endl;
    }
    else if (k == 4)
    {
        bool f3 = false;
        int f2 = 0;
        int f1 = 0;
        REP(i, n)
        {
            if (a[i] % 4 == 0)
            {
                cout << 0 << endl;
                return;
            }
            else if (a[i] % 4 == 3)
                f3 = true;
            else if (a[i] % 4 == 1)
                f1++;
            else if (a[i] % 4 == 2)
                f2++;
        }
        if (f2 >= 2)
            cout << 0 << endl;
        else if (f3 or f1 >= 1 and f2 >= 1)
            cout << 1 << endl;
        else if (f1 >= 2 or f2 == 1)
            cout << 2 << endl;
        else
            cout << 3 << endl;
    }
    else if (k == 5)
    {
        int ans = inf;
        REP(i, n)
        {
            if (a[i] % 5 == 0)
            {
                cout << 0 << endl;
                return;
            }
            else
                ans = min(ans, 5 - a[i] % 5);
        }
        cout << ans << endl;
    }
}
 
int main()
{
    IOS;
    int t = 1;
    cin >> t;
    while (t--)
        solve();
 
    return 0;
}

D. In Love

​ 题意:初始有一个空的集合,有 q 次操作,每次操作有两种:

​ 1、+ l r 将区间 [l, r] 加到集合中;

​ 2、- l r 将区间 [l, r] 从集合中移除(保证 [l, r] 存在于集合中)

​ 每次操作后需要回答在集合中是否至少存在一对互不相交的区间。

​ 思路:二分优化。首先贪心地想,只要每次判断集合中是否存在大于最左面的右端点的左端点即可。所以我们将每个区间左右端点分开来存储,接着每次找到最左面的右端点的位置,并在左端点集合二分查找是否存在大于该右端点的左端点即可。

void solve()
{
    int q;
    cin >> q;
    int n = 0;
    map l, r;
 
    auto check = [&]() -> bool {
        if (n <= 1)
            return false;
        auto p = l.upper_bound((*r.begin()).fs);
        return p != l.end();
    };
 
    while (q--)
    {
        char op;
        pi a;
        cin >> op >> a.fs >> a.sc;
        if (op == '+')
        {
            n++;
            l[a.fs]++;
            r[a.sc]++;
            cout << (check() ? "YES" : "NO") << endl;
        }
        else
        {
            n--;
            if (--l[a.fs] == 0)
                l.erase(a.fs);
            if (--r[a.sc] == 0)
                r.erase(a.sc);
            cout << (check() ? "YES" : "NO") << endl;
        }
    }
}
 
int main()
{
    IOS;
    int t = 1;
    // cin >> t;
    while (t--)
        solve();
 
    return 0;
}

E. Look Back

初见还在想这么简单的题怎么会在 E,后来才发现简单的是我(捂脸)

​ 题意:给你一个长度为 n 的数组,每次操作可以将其中一个元素乘二,问至少需要多少次操作使整个数组变为一个非递减数组。

​ 思路:暴力会溢出精度,如果用 python 等则会超时,所以考虑找规律。

​ 如果仔细观察,就会发现,对于相等的两个相邻数 a 和 b,若 a 在此前乘了 2 k 2^k 2k,那么为了使数组非递减,则 b 也需要乘以 2 k 2^k 2k。所以,对于不相等的两个相邻数,只需要在继承前一个数已乘次数的基础上加上后一个数使数列合法的操作次数即可。

具体而言,对于两个相邻数(未操作前) a 和 b:

​ 1、若 a > b :假设 a = 7,b = 2,则 b 需要乘以 2 2 2^2 22 才可以满足条件。若此时 a 已经乘以 2 k 2^k 2k ,则 b 也需要再乘以 2 k 2^k 2k 才可以使其满足条件,所以若 a 需要操作 k 次,则 b 需要操作 k + 2 次,所以我们需要计算 b 需要至少乘以几个 2 才可以大于等于 a;

​ 2、若 a <= b :假设 a = 5,b = 15,若 a 此时已经乘以 2 3 2^3 23 ,变成 5 * 2 3 2^3 23 = 40 了,此时贪心地去思考 b,若 b 也进行 3 次操作,固然可以满足条件,但并不是最优的,我们需要最少的操作数来操作 b,即 b 再少乘一次就会小于 a。此时观察原先的 a 和 b,b 只要除以一个 2 就可以仅仅大于 a,也就是说,b 可以再继承 a 的操作次数的基础上减少 1 次,所以反过来想,我们可以计算 a 至少需要乘以几个 2 才可以仅仅小于 b。

​ 对于最后的答案,我们只要累加每个数的操作次数即可。

#define rep(i, a, b) for (int i = a; i <= b; i++)
using ll = long long;
using vll = vector;

void solve()
{
    int n;
    cin >> n;
    vll a(n + 1), ans(n + 1);
    rep(i, 1, n) cin >> a[i];
    rep(i, 1, n)
    {
        if (a[i] < a[i - 1])
        {
            ll x = a[i], y = a[i - 1];
            int t = 0;
            while (x < y)
            {
                x <<= 1;
                t++;
            }
            ans[i] = ans[i - 1] + t;
        }
        else if (i > 1)
        {
            ll x = a[i], y = a[i - 1];
            int t = 0;
            while ((y << 1) <= x)
            {
                y <<= 1;
                t++;
            }
            ans[i] = max(0ll, ans[i - 1] - t);	//注意操作次数不能为负数
        }
    }
    cout << accumulate(all(ans), 0ll) << endl;
}
 
int main()
{
    IOS;
    int t = 1;
    cin >> t;
    while (t--)
        solve();
 
    return 0;
}

F. You Are So Beautiful

​ 题意:给你一个长度为 n 的数组 a,计算出满足条件的子数组的数量。

​ 对于满足条件的子数组,它在数组 a 的所有子序列中仅出现一次。

​ 思路:找规律,对于满足条件的子数组 [l, r],发现它的首元素在 a 中下标 [1, l] 中仅出现一次,尾元素在 a 中下标 [r, n] 中也仅出现一次。

​ 证明:考虑反证法,对于满足条件的子数组 [l, r],若 a l a_l al 在 [1, l] 中不是第一次出现,即存在下标 i ∈ \in [1, l) ,使得 a i a_i ai = a l a_l al,则必存在子序列 { a i a_i ai, a l + 1 a_l+1 al+1, a l + 2 a_l+2 al+2, …, a r a_r ar} 与原子数组 [l, r] 相等,与条件相悖。 a r a_r ar 同理。

所以证明成立。

​ 因此,只要分别从前往后和从后往前扫一遍数组 a,找到每一个第一次出现的数即可。

#define all(x) (x).begin(), (x).end()
void solve()
{
    int n;
    cin >> n;
    vi a(n + 1);
    rep(i, 1, n) cin >> a[i];
    set vpre, vsuf;
    vi pre, suf;
    rep(i, 1, n)
    if (not vpre.count(a[i]))
        pre.pb(i), vpre.emplace(a[i]);
 
    per(i, 1, n)
    if (not vsuf.count(a[i]))
        suf.pb(i), vsuf.emplace(a[i]);
 
    sort(all(suf));
 
    ll ans = 0;
    for (int si : pre)
    {
        int p = suf.end() - lower_bound(all(suf), si);
        // debug(si, p);
        ans += p;
    }
    cout << ans << endl;
}
 
int main()
{
    IOS;
    int t = 1;
    cin >> t;
    while (t--)
        solve();
 
    return 0;
}

G. Dances

​ 题意:原题是给两个长度均为 n 的数组 a 和 b。每次操作可以分别从 a 和 b 中删除一个元素。可以对 a 和 b 任意排序,问使 a 中每一个元素都严格小于 b 中对应位置元素的最少操作次数是多少?现在新题是在原题基础上多了一个变量 m,问 a 1 a_1 a1 为 [1, m] 中的每一个值后所得到的所有答案的和是多少?

​ 思路:二分查找。对于数组 a 和 b,一定存在一个最小的 k,使得 a 和 b 删去 k 个元素后满足题意,于是我们可以二分查找这个 k。

​ 现在考虑这个不确定的 a 1 a_1 a1。设 f(i) 为 a 1 a_1 a1 = i 时所得到的 k,通过手玩几个样例可以发现无论 i 取多大,所有 k 的值不会相差超过 1,且存在一个值 x,使得 f(1) = f(2) = … = f(x) - 1 = f(x + 1) - 1 = … = f(inf) - 1,所以我们可以二分查找这个 x。

void solve()
{
    int n, m;
    cin >> n >> m;
    deque a(n), b(n);
    rep(i, 1, n - 1) cin >> a[i];
    REP(i, n) cin >> b[i];
 
    auto check1 = [&](int k, deque aa, deque bb) -> bool
    {
        for (int i = n - 1; i > n - 1 - k; i--)
            aa.pop_back();
        for (int i = 0; i < k; i++)
            bb.pop_front();
        // debug(aa.size(), bb.size());
        REP(i, n - k)
        if (aa[i] >= bb[i])
            return false;
        return true;
    };
 
    auto findk = [&](ll x) -> int
    {
        auto aa = a, bb = b;
        aa[0] = x;
        sort(all(aa));
        sort(all(bb));
        int l = 0, r = n;
        while (l < r)
        {
            int mid = l + r >> 1;
            if (check1(mid, aa, bb))
                r = mid;
            else
                l = mid + 1;
        }
        return l;
    };
 
    int f = findk(1);
 
    ll l = 1, r = INF;
    while (l < r)
    {
        ll mid = l + r >> 1ll;
        // debug(findk(mid));
        if (findk(mid) > f)
            r = mid;
        else
            l = mid + 1;
    }
    ll ans = 0;
    // debug(l, f);
    if (m >= l)
        ans = 1ll * (l - 1) * f + 1ll * (m - l + 1) * (f + 1);
    else 
        ans = 1ll * m * f;
    cout << ans << endl;
}
 
int main()
{
    IOS;
    int t = 1;
    cin >> t;
    while (t--)
        solve();
 
    return 0;
}

你可能感兴趣的:(1024程序员节,c++)