Codeforces Round #840 (Div. 2) and Enigma 2022 - Cybros LNMIIT (AC + B D)

本文介绍了四个关于数组操作的问题及其解决方案。问题A涉及到数组元素的位操作,目标是找到最大值;问题B关注如何通过有限次攻击击败所有怪兽;问题C探讨如何通过特定操作使数组和最大化;问题D研究满足特定条件的排列计数。每个问题都提供了思路和参考代码,涉及数组操作、模拟、组合数学等算法。
摘要由CSDN通过智能技术生成

A. Absolute Maximization

题意:

给你一个长度大于等于3的数组a,可以进行任意次操作,一次操作中可以任选两个数,交换两个数二进制下的一个位。

题解:

简单思维题。数组大小大于等于3,可以通过任意次数把每个位的1或0放到一个数上,所以最大值就是所有数的或值(将数位上有1的移到同一个数上),最小值为所有数的按位与值(尽可能使数位能为0就为0)

参考代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 10;
const int mod = 1e9 + 7;
#define pii pair<int, int>

int n, m, a[N];

void solve()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    int mx = 0, mn = ~0;
    for (int i = 1; i <= n; i++)
        mx |= a[i], mn &= a[i];
    cout << mx - mn << "\n";
}

signed main()
{
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    int T;
    cin >> T;
    while (T--)
        solve();
}

B. Incinerate

题意:

有n个怪兽,每个怪兽有一个健康值h,一个能量值p。每次攻击可以对每个怪兽造成k点伤害,健康值-k,每次攻击结束后,攻击值k将减去还没死的怪兽(h>0)中最小的p值。问最后能否击败所有怪兽

题解:

直接模拟的复杂度实际上是O(nk)的,写题还是得注意复杂度,不要写假了。。

将怪兽按p值从小到大排序,记录总的攻击值,记录当前还活着的最小p值的怪兽,当生命值小于总的攻击值时,跳过该怪兽。最后判断k<=0时是否还有存活的怪兽即可。

复杂度大致为O(max(n,k))。

参考代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 10;
const int mod = 1e9 + 7;
#define pii pair<int, int>

int n, k, h[N], p[N];
struct node
{
    int h, p;
    bool operator<(const node x) const
    {
        return p < x.p;
    }
} a[N];
void solve()
{
    cin >> n >> k;
    for (int i = 1; i <= n; i++)
        cin >> a[i].h;
    for (int j = 1; j <= n; j++)
        cin >> a[j].p;
    sort(a + 1, a + n + 1);
    int cnt = 0, u = 1; // cnt为当前总的攻击值,u为当前p最小的怪兽下标
    while (k > 0)
    {
        cnt += k;
        int mn = 1e9;
        while (u <= n && a[u].h - cnt <= 0) // h值已经小于0
            u++;
        if (u == n + 1) // 所有的怪兽的h都小于了0
        {
            puts("YES");
            return;
        }
        mn = a[u].p;
        // 以下注释为复杂度O(nk)代码(直接模拟)
        //  for (int i = 1; i <= n; i++)
        //      h[i] -= k;
        //  for (int i = 1; i <= n; i++)
        //      if (h[i] - cnt > 0 && p[i] < mn)
        //          mn = p[i];
        k -= mn;
    }
    for (int i = 1; i <= n; i++)
    {
        if (a[i].h - cnt > 0) // 当还有怪兽h值大于0则不能击败所有
        {
            puts("NO");
            return;
        }
    }
    puts("YES");
}

signed main()
{
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    int T;
    cin >> T;
    while (T--)
        solve();
}

C. Another Array Problem

题意:

给你一个数组a,在一次操作中可以选择一个区间[l,r]将a[l]~a[r]全部变成a[l]-a[r]的绝对值,操作可以进行任意次。问最后数组和的最大值可以为多少。

题解:

容易想到,我们尽可能将数组变成差值最大的数。显然,最小值是一定能变成0的(将区间长度为2进行两次操作就能变为0)

n>=4时,显然我们能得到最小值0,也能得到这个数组的最大值,并最终将这个数组所有数变成这个最大值(我们可以选数组前两个数或后两个数变成0,再与最大的数进行一次操作,再重复类似上述操作可将数组变成全为最大值)

n == 3时,最大值如果在1或3位置按上述方法是一定能取到的,所以只需要再讨论下最大值在2位置的情况,很显然数组每个数的最大值只能是相邻差的最大值

n==2时,显然数组每个数的最大值也只能是相邻差的最大值

参考代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 10;
const int mod = 1e9 + 7;
#define pii pair<int, int>

int n, m, a[N];

void solve()
{
    int ans = 0;
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i], ans += a[i]; // 进行0次操作的值
    if (n == 2)                   // 等于2时的特殊情况
        ans = max(ans, n * abs(a[1] - a[2]));
    else if (n == 3) // 等于3时的特殊情况
        ans = max(ans, n * max(abs(a[1] - a[2]), abs(a[2] - a[3])));
    int mx = 0;
    for (int i = 3; i <= n; i++) // 选a[1],a[2]变为0
        mx = max(mx, a[i]);
    ans = max(ans, mx * n);

    mx = 0;
    for (int i = 1; i <= n - 2; i++) // 选后两个数为最大值
        mx = max(mx, a[i]);
    ans = max(ans, mx * n);
    cout << ans << "\n";
}

signed main()
{
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    int T;
    cin >> T;
    while (T--)
        solve();
}

D. Valid Bitonic Permutations

题意:

给出5个数n,i,j,x,y分别代表排列的大小、a[i]=x,a[j]=y。问满足条件的严格先单调增再单调减的排列的个数。

题解:

这里提供组合数学解法。

为了方便我们把x,y经过等价转换变成x<y的情况。因为只有一次增和一次减,那么最大值n一定是分界点,所以我们只需要考虑n的位置。

情况一:

(图有点丑,凑活下吧。蓝色的线代表增减性) 

最大值在x、y的右边,很显然根据单调性可以分成四个段记为a,b,c,d。考虑其中三个段能选的数的方案数,因为每一段都要满足一个单调性,所以数确定了那么位置也是确定的。

a段:在x-1个数中需要选u-1个即C(x - 1, u - 1)

b段:在y-x-1个数中需要选出v-u-1个即C(y - x - 1, v - u - 1)

c段:在n-y-1个数中需要选出i-v-1(i为n的位置下标)个即C(n - y - 1, i - v - 1)

方案数为:

         \sum_{i=v+1}^{n-1}C(x - 1, u - 1) * C(y - x - 1, v - u - 1) % mod * C(n - y - 1, i - v - 1) % mod

 情况二:

 最大值在x、y的中间。同情况一进行讨论

a段:在x-1个数中取u-1个即C(x - 1, u - 1)

b、c段的讨论有点不同,有个小细节。我们记 it = (n - y - 1) - (v - i - 1),该数的含义是能放在c段的数减掉放在了c段的数剩下的数的个数,因为这些数大于y不能放在d段,所以只能强制放在b段(因为也只有b能够放了)

当it > (i - u - 1)时(必须放在b段的数比b段能容纳的数还要多):没有满足条件的方案,方案数为0

当it <= (i - u - 1)时:

        c段:在n-y-1个数中取v-i-1个即C(n - y - 1, v - i - 1)

        b段:在n - x - (v - i + 1) - it个数中取i - u - 1 - it即C(n - x - (v - i + 1) - it, i - u - 1 - it)

 方案数为:

\sum_{i=u+1}^{v-1}C(x - 1, u - 1) * C(n - x - (v - i + 1) - it, i - u - 1 - it) % mod * C(n - y - 1, v - i - 1) % mod * [it<=i-u-1]

[it<=i-u-1]表达式为真则为1,为假为0

 参考代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 10;
const int mod = 1e9 + 7;
#define pii pair<int, int>

int n, u, v, x, y, a[N], c[110][110];
int C(int n, int m)
{
    if (m > n || m < 0 || n < 0) // 避免不合法情况
        return 0;
    return c[n][m];
}
void solve()
{
    cin >> n >> u >> v >> x >> y;
    int ans = 0;
    if (x > y)
    {
        swap(x, y), swap(u, v), u = n - u + 1, v = n - v + 1;
    }
    if (y == n) // 如果y就是最大值那就不需要再考虑n的位置了
    {
        if (n != v)
            ans += (v != u ? C(n - x - 1, v - u - 1) : 1) * C(x - 1, u - 1) % mod;
        ans %= mod;
    }
    else
    {
        // 情况一
        for (int i = v + 1; i < n; i++) // 小细节:不能在n位置,因为必须有单调减的段
        {
            ans += C(x - 1, u - 1) * C(y - x - 1, v - u - 1) % mod * C(n - y - 1, i - v - 1) % mod;
            ans %= mod;
        }
        // 情况二
        for (int i = u + 1; i <= v - 1; i++)
        {
            int it = n - y - 1 - (v - i - 1); // 能放在c段的数减掉放在了c段的数剩下的数的个数
            if (it > i - u - 1)               // 不存在方案
                continue;
            ans += C(x - 1, u - 1) * C(n - x - (v - i + 1) - it, i - u - 1 - it) % mod * C(n - y - 1, v - i - 1) % mod;
            ans %= mod;
        }
    }
    cout << ans % mod << "\n";
}

signed main()
{
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    c[0][0] = 1;
    for (int i = 1; i <= 100; i++) // 预处理组合数
    {
        c[i][0] = 1;
        for (int j = 1; j <= i; j++)
        {
            c[i][j] = c[i - 1][j] + c[i - 1][j - 1];
            c[i][j] %= mod;
        }
    }
    int T;
    cin >> T;
    while (T--)
        solve();
}

upd: dp方法

dp[i][j]:构造区间[ i , j]的合法方案数。

我们考虑不断扩展[i,j]的左边和右边来进行状态转移,实际上我们可以从n到1进行填数扩展,来求得所有的合法方案数。

转移方程:

dp[i-1][j]=dp[i-1][j]+dp[i][j]      当前的最大数能填在i的左边(不与所给条件矛盾)

dp[i][j + 1] = dp[i][j + 1] + dp[i][j]      当前的最大数能填在j的右边

初始化:区间长度为1能填入n的方案数为1

详见代码:

int n, u, v, x, y, a[N], dp[110][110];
bool check(int id, int val)
{
    if (id == u && val != x)
        return false;
    if (id == v && val != y)
        return false;
    return true;
}
void solve()
{
    memset(dp, 0, sizeof dp);
    cin >> n >> u >> v >> x >> y;
    for (int i = 2; i < n; i++)
        if (check(i, n))
            dp[i][i] = 1;
    for (int i = n; i >= 1; i--)
    {
        for (int j = i; j <= n; j++)
        {
            int val = n - (j - i + 1);
            if (check(i - 1, val))
                dp[i - 1][j] = (dp[i - 1][j] + dp[i][j]) % mod;
            if (check(j + 1, val))
                dp[i][j + 1] = (dp[i][j + 1] + dp[i][j]) % mod;
        }
    }
    cout << dp[1][n] << endl;
}

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

self_disc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值