Educational Codeforces Round 96 (Rated for Div. 2)

201 篇文章 0 订阅
157 篇文章 1 订阅
传送门

Educational Codeforces Round 96 (Rated for Div. 2)

A

多重部分和问题, d p [ i ] [ j ] dp[i][j] dp[i][j] 代表使用 0 − i 0-i 0i 种数字是否能组合为 j j j
d p [ i ] [ j ] = d p [ i − 1 ] [ j ]   ∣   d p [ i ] [ j − N [ i ] ] dp[i][j] = dp[i-1][j]\ |\ dp[i][j-N[i]] dp[i][j]=dp[i1][j]  dp[i][jN[i]] 动态规划的同时记录任一可行解。

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
using namespace std;
#define maxn 1005
int N[3] = {3, 5, 7};
int dp[maxn], rec[maxn][3];

int main()
{
    int sz = sizeof(int) * 3;
    dp[0] = 1;
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < maxn - N[i]; j++)
        {
            if (dp[j])
            {
                dp[j + N[i]] = 1;
                memcpy(rec[j + N[i]], rec[j], sz);
                ++rec[j + N[i]][i];
            }
        }
    }
    int t, n;
    scanf("%d", &t);
    while (t--)
    {
        scanf("%d", &n);
        if (dp[n])
        {
            printf("%d %d %d\n", rec[n][0], rec[n][1], rec[n][2]);
        }
        else
        {
            puts("-1");
        }
    }
}
B

贪心地从 [ 1 , k ] [1,k] [1,k] 多的水瓶向最多的水平倒水。

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
using namespace std;
#define maxn 200005
typedef long long ll;
int a[maxn];

int main()
{
    int t, n, k;
    scanf("%d", &t);
    while (t--)
    {
        scanf("%d%d", &n, &k);
        for (int i = 0; i < n; i++)
        {
            scanf("%d", a + i);
        }
        sort(a, a + n);
        int lb = max(0, n - k - 1);
        ll res = 0;
        for (int i = lb; i < n; i++)
        {
            res += a[i];
        }
        printf("%lld\n", res);
    }
}
C

最终答案大于 1 1 1,那么最优解为 2 2 2。贪心地取最大的两个数进行合并,最后一步为 3 3 3 1 1 1 合并。

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
#define maxn 200005
int rec[maxn][2];

int main()
{
    int t, n;
    scanf("%d", &t);
    while (t--)
    {
        scanf("%d", &n);
        priority_queue<int> q;
        for (int i = 1; i <= n; ++i)
        {
            q.push(i);
        }
        int k = 0;
        while (q.size() >= 2)
        {
            int a = q.top();
            q.pop();
            int b = q.top();
            q.pop();
            rec[k][0] = a, rec[k++][1] = b;
            q.push((a + b + 1) >> 1);
        }
        printf("%d\n", q.top());
        for (int i = 0; i < k; ++i)
        {
            printf("%d %d\n", rec[i][0], rec[i][1]);
        }
    }
}
D

贪心地取最早被操作二删除的(即最靠串首)的连续 0 0 0 1 1 1 其中一个进行操作一;若串中不存在连续的 0 0 0 1 1 1,那么每一轮操作一可以从串首或串尾进行删除,然后进行操作二,每轮删除 2 2 2 个字符。

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <set>
using namespace std;
#define maxn 200005
int cnt[maxn];
char s[maxn];

int main()
{
    int t, n;
    scanf("%d", &t);
    while (t--)
    {
        scanf("%d", &n);
        scanf(" %s", s);
        set<int> st;
        int k = 0, pre = -1, cur = 0;
        while (cur < n)
        {
            while (cur + 1 < n && s[cur] == s[cur + 1])
                ++cur;
            cnt[k] = cur - pre;
            if (cnt[k] > 1)
                st.insert(k);
            ++k;
            pre = cur, cur = pre + 1;
        }
        int step = 0, l = 0, r = k;
        while (l < r)
        {
            while (!st.empty() && *st.begin() < l)
            {
                st.erase(st.begin());
            }
            if (st.empty())
            {
                step += (r - l + 1) >> 1;
                break;
            }
            else
            {
                if (cnt[l] > 1)
                {
                }
                else
                {
                    if (--cnt[*st.begin()] == 1)
                    {
                        st.erase(st.begin());
                    }
                }
                ++l;
            }
            ++step;
        }
        printf("%d\n", step);
    }
}
E

记录字符集各自出现的位置,将字符串反转,从后向前遍历,每次取原串离当前反串位置最近的字符向后进行交换;用 B I T BIT BIT 维护原串字符交换后各字符的相对索引值。

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
using namespace std;
#define maxn 200005
#define base 26
int n, pos[base][maxn], ptr[base];
int bit[maxn];
char s[maxn];

void add(int i, int x)
{
    while (i <= n)
    {
        bit[i] += x;
        i += i & -i;
    }
}

int sum(int i)
{
    int s = 0;
    while (i > 0)
    {
        s += bit[i];
        i -= i & -i;
    }
    return s;
}

int main()
{
    scanf("%d %s", &n, s);
    for (int i = 0; i < n; i++)
    {
        int c = s[i] - 'a';
        pos[c][ptr[c]++] = i;
    }
    for (int i = 1; i <= n; i++)
    {
        add(i, 1);
    }
    reverse(s, s + n);
    long long res = 0;
    for (int i = n - 1; i >= 0; i--)
    {
        int c = s[i] - 'a', j = pos[c][--ptr[c]];
        res += sum(n) - sum(j + 1);
        add(j + 1, -1);
    }
    printf("%lld\n", res);
}
F 补题

∑ a [ i ] \sum a[i] a[i] 是一定要消耗的子弹数量,目标是最小化丢弃的子弹数。 d p [ i ] [ j ] dp[i][j] dp[i][j] 代表第 i i i 轮开始剩余 j j j 颗子弹,设 r r r 为第 i i i 轮结束剩余的子弹,则有重新填充与保留子弹两种选择
{ d p [ i + 1 ] [ k ] = m i n { d p [ i ] [ j ] + j } d p [ i + 1 ] [ r ] = m i n { d p [ i ] [ j ] } \begin{cases}dp[i+1][k] = min\{dp[i][j] + j\}\\ dp[i+1][r] = min\{dp[i][j]\}\\ \end{cases} {dp[i+1][k]=min{dp[i][j]+j}dp[i+1][r]=min{dp[i][j]} 每一轮剩余各种子弹数的情况都对应了一种子弹剩余的情况,且可能进行重新填充弹药,那么第 i i i 状态总数不超过 i + 1 i+1 i+1,那么总状态数 O ( n 2 ) O(n^2) O(n2),用 m a p map map 维护。

考虑到第 i + 1 i+1 i+1 轮前可以填充弹药,需要满足 l i + 1 > r i l_{i+1}>r_i li+1>ri 或者弹药消灭怪兽的时间 t < r i t<r_i t<ri,那么按每一轮进行 D P DP DP 时需要额外记录是否能够填充弹药的信息。

#include <algorithm>
#include <climits>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <map>
using namespace std;
#define maxn 2005
typedef long long ll;
struct node
{
    ll n;
    bool f;
};
typedef map<int, node> mp;
typedef mp::iterator ite;
int N, K, L[maxn], R[maxn], A[maxn];
mp dp[maxn];

inline void rem(int a, int j, int &r, int &d)
{
    if (a <= j)
    {
        r = j - a, d = 0;
    }
    else
    {
        d = (a - j + K - 1) / K, r = d * K - (a - j);
    }
}

inline bool judge(int i, int r, bool f, bool c)
{
    int n = R[i] - L[i];
    if (f)
    {
        n += (c || L[i] > R[i - 1]) ? 1 : 0;
        return A[i] <= 1LL * n * K;
    }
    return A[i] <= 1LL * n * K + r;
}

int main()
{
    scanf("%d%d", &N, &K);
    ll s = 0;
    for (int i = 0; i < N; i++)
    {
        scanf("%d%d%d", L + i, R + i, A + i);
        s += A[i];
    }
    dp[0][K] = node{0, 0};
    for (int i = 0; i < N; i++)
    {
        for (ite it = dp[i].begin(); it != dp[i].end(); ++it)
        {
            int j = it->first, r, d;
            node &cur = it->second;
            if (judge(i, j, 0, cur.f))
            {
                rem(A[i], j, r, d);
                node &nxt = dp[i + 1][r];
                if (!nxt.n || cur.n < nxt.n)
                {
                    nxt.n = cur.n, nxt.f = d < R[i] - L[i];
                }
            }
            if (j < K)
            {
                if (judge(i, 0, 1, cur.f))
                {
                    rem(A[i], (cur.f || L[i] > R[i - 1]) ? K : 0, r, d);
                    node &nxt = dp[i + 1][r];
                    if (!nxt.n || cur.n + j < nxt.n || (cur.n + j == nxt.n && !nxt.f))
                    {
                        nxt.n = cur.n + j, nxt.f = d < R[i] - L[i];
                    }
                }
            }
        }
    }
    ll res = LLONG_MAX;
    for (ite it = dp[N].begin(); it != dp[N].end(); ++it)
    {
        res = min(res, it->second.n);
    }
    printf("%lld\n", res == LLONG_MAX ? -1 : res + s);
    return 0;
}

考虑 m a p map map 以及记录额外信息的开销,修改动态规划的定义, d p [ i ] dp[i] dp[i] 代表到达第 i i i 轮开始弹药满匣的最小花费,那么枚举 i i i 迭代更新 d p [ j ] , j > i dp[j],j>i dp[j],j>i

#include <algorithm>
#include <climits>
#include <cmath>
#include <cstdio>
#include <cstring>
using namespace std;
#define maxn 2005
typedef long long ll;
ll N, K, L[maxn], R[maxn], A[maxn], dp[maxn];

inline void rem(int i, ll &r, ll &d)
{
    if (A[i] <= r)
    {
        r -= A[i], d = 0;
    }
    else
    {
        d = (A[i] - r + K - 1) / K, r = d * K - (A[i] - r);
    }
}

inline bool judge(int i, ll r)
{
    return A[i] <= (R[i] - L[i]) * K + r;
}

int main()
{
    scanf("%lld%lld", &N, &K);
    ll s = 0;
    for (int i = 0; i < N; i++)
    {
        scanf("%lld%lld%lld", L + i, R + i, A + i);
        s += A[i];
    }
    fill(dp, dp + N, LLONG_MAX);
    dp[0] = 0;
    ll res = LLONG_MAX;
    for (int i = 0; i < N; i++)
    {
        ll r = K, d, tmp = dp[i];
        for (int j = i; j < N; j++)
        {
            if (tmp == LLONG_MAX || !judge(j, r))
            {
                break;
            }
            else
            {
                rem(j, r, d);
                if (j + 1 < N)
                {
                    if (d < R[j] - L[j] || R[j] < L[j + 1])
                    {
                        dp[j + 1] = min(dp[j + 1], tmp + r);
                    }
                }
                else
                {
                    res = min(res, tmp);
                }
            }
        }
    }
    printf("%lld\n", res == LLONG_MAX ? -1 : res + s);
    return 0;
}
G 补题
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值