Codeforces Round #672 (Div. 2)

Codeforces Round #672 (Div. 2)

C1. Pokémon Army (easy version)

题意: 给定n个数字,可以从这n个数字中选择k个,使得 r e s = a 1 − a 2 + a 3 − a 4 + . . . + a k res = a_1 - a_2 + a_3 - a_4 +... + a_k res=a1a2+a3a4+...+ak,要求找到合适的k,使得res最大。 1 < = n < = 3 ∗ 1 0 5 1<=n<=3*10^5 1<=n<=3105

题解: 状态机dp。 f [ i ] [ 0 ] f[i][0] f[i][0]:选择完前i个物品且最后一个加入的数字前面是减号的最大值; f [ i ] [ 1 ] f[i][1] f[i][1]:选择完前i个物品且最后一个加入的数字前面是加号的最大值。

因此转移方程为:

f [ i ] [ 1 ] = m a x ( f [ i − 1 ] [ 1 ] , f [ i − 1 ] [ 0 ] + a [ i ] , a [ i ] ) f[i][1] = max({f[i - 1][1], f[i - 1][0] + a[i], a[i]}) f[i][1]=max(f[i1][1],f[i1][0]+a[i],a[i])

f [ i ] [ 0 ] = m a x ( f [ i − 1 ] [ 0 ] , f [ i − 1 ] [ 1 ] − a [ i ] ) f[i][0] = max(f[i - 1][0], f[i - 1][1] - a[i]) f[i][0]=max(f[i1][0],f[i1][1]a[i])

答案为: m a x ( f [ n ] [ 0 ] , f [ n ] [ 1 ] ) max(f[n][0], f[n][1]) max(f[n][0],f[n][1])

代码:

#include <bits/stdc++.h>

#define int long long
using namespace std;

int const MAXN = 3e5 + 10;
int n, m, T, a[MAXN], f[MAXN][2];

signed main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cin >> T;
    while(T--) {
        int q;
        cin >> n >> q;
        for (int i =1 ; i <= n; ++i) cin >> a[i], f[i][0] = f[i][1] = 0;
        for (int i = 1; i <= n; ++i) {
            f[i][1] = max({f[i - 1][1], f[i - 1][0] + a[i], a[i]});
            f[i][0] = max(f[i - 1][0], f[i - 1][1] - a[i]);
        }
        cout << max(f[n][1], f[n][0]) << endl;
    }
    return 0;
}

/*
3
3 0
1 3 2
2 0
1 2
7 0
1 2 5 4 3 6 7
*/

C2. Pokémon Army (hard version)

题意: 给定n个数字,可以从这n个数字中选择k个,使得 r e s = a 1 − a 2 + a 3 − a 4 + . . . + a k res = a_1 - a_2 + a_3 - a_4 +... + a_k res=a1a2+a3a4+...+ak,要求找到合适的k,使得res最大。同时,有q次操作,每次操作输入一个l和r,然后交换a[l]和a[r],再次询问res的最大值。 1 < = n < = 3 ∗ 1 0 5 1<=n<=3*10^5 1<=n<=3105

题解: 线段树维护区间最大值:维护数组 f [ 2 ] [ 2 ] f [ 2 ] [ 2 ] f[2][2]

f [ 0 ] [ 0 ] f[0][0] f[0][0]表示序列开头+,结尾+。
f [ 0 ] [ 1 ] f [ 0 ] [ 1 ] f[0][1] 表示序列开头+,结尾-。
f [ 1 ] [ 0 ] f [ 1 ] [ 0 ] f[1][0]表示序列开头-,结尾+。
$f [ 1 ] [ 1 ] 表 示 序 列 开 头 − , 结 尾 − 。 每 次 进 行 交 换 操 作 后 , 进 行 暴 力 修 改 , 重 新 维 护 f 数 组 即 可 , 然 后 打 印 表示序列开头-,结尾-。 每次进行交换操作后,进行暴力修改,重新维护f数组即可,然后打印 ff[0].val$

代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
const int N = 300010;
const LL INF = 1e17;

LL a[N];
int n, q;
// f[0][0] ++
// f[0][1] +-
// f[1][0] -+
// f[1][1] --
struct node {
    int l, r;
    LL val;
    LL f[2][2];
} tree[N * 4];
void pushup(int u) {
    tree[u].val = -INF;
    for (int i = 0; i < 2; i++)
        for (int j = 0; j < 2; j++) {
            tree[u].f[i][j] =
                max(tree[u << 1].f[i][j], tree[u << 1 | 1].f[i][j]);
            tree[u].f[i][j] =
                max(tree[u].f[i][j],
                    max(tree[u << 1].f[i][0] + tree[u << 1 | 1].f[1][j],
                        tree[u << 1].f[i][1] + tree[u << 1 | 1].f[0][j]));
            tree[u].val = max(tree[u].val, tree[u].f[i][j]);
        }
}

void build(int u, int l, int r) {
    tree[u] = {l, r};
    if (l == r) {
        tree[u].val = a[l];
        tree[u].f[0][0] = a[l];
        tree[u].f[1][1] = -a[l];
        tree[u].f[0][1] = tree[u].f[1][0] = -INF;
        return;
    }
    int mid = l + r >> 1;
    build(u << 1, l, mid);
    build(u << 1 | 1, mid + 1, r);
    pushup(u);
}

void modify(int u, int p, int x) {
    if (tree[u].l == tree[u].r) {
        tree[u].val = x;
        tree[u].f[0][0] = x;
        tree[u].f[1][1] = -x;
        tree[u].f[0][1] = tree[u].f[1][0] = -INF;
        return;
    }
    int mid = tree[u].l + tree[u].r >> 1;
    if (p <= mid)
        modify(u << 1, p, x);
    else
        modify(u << 1 | 1, p, x);
    pushup(u);
}

int main() {
    ios::sync_with_stdio(false);
    cout.tie(0);
    int T;
    cin >> T;
    while (T--) {
        cin >> n >> q;
        for (int i = 1; i <= n; i++) cin >> a[i];
        build(1, 1, n);
        cout << tree[1].val << '\n';
        while (q--) {
            int l, r;
            cin >> l >> r;
            modify(1, l, a[r]);
            modify(1, r, a[l]);
            cout << tree[1].val << '\n';
            swap(a[l], a[r]);
        }
    }
    return 0;
}

D. Rescue Nibel!

题意: 给出 n 盏灯,每盏灯在 [ l , r ] 这段时间内会保持点亮的状态,问恰好有 k 盏灯同时点亮,有多少种组合方式。 1 < = n < = 3 ∗ 1 0 5 1<=n<=3*10^5 1<=n<=3105

题解: 组合计数+扫描线思想。先把每个区间的左端点和右端点拆开记录下来,然后排个序(注意:先按照位置排序,按照左端点优先原则排序)借用扫描线的思想扫描整个序列。维护一个cnt,表示到当前这个点时,在它的区间的右端点的数目。扫描的过程中,如果到一个左端点说明目前有一盏灯在此时刻点亮那么对答案的贡献就是在需要从前面点亮的灯中选择k − 1盏灯,维护前面点亮灯的数量只需要维护一个cnt即可,那么对答案的贡献即 C c n t k − 1 C_{cnt}^{k-1} Ccntk1,然后更新cnt即可,如果扫描到一个右端点不难发现对答案没有贡献,只需要维护cnt即可。

代码:

#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> PII;
typedef long long LL;
const int N = 300010;
const LL mod = 998244353;
PII a[2 * N];
int n, k;

LL qmi(LL a, LL b, LL p) {
    LL res = 1;
    while (b) {
        if (b & 1) res = res * a % p;
        b >>= 1;
        a = a * a % p;
    }
    return res;
}

LL fact[N], infact[N];
void init(int n) {
    fact[0] = infact[0] = 1;
    for (int i = 1; i <= n; i++) {
        fact[i] = fact[i - 1] * i % mod;
        infact[i] = qmi(fact[i], mod - 2, mod);
    }
}

int main() {
    cin >> n >> k;
    init(n);
    for (int i = 1; i <= n; i++) {
        int l, r;
        cin >> l >> r;
        a[i].first = l, a[i].second = -1;         //左端点
        a[i + n].first = r, a[i + n].second = 1;  //右端点
    }
    sort(a + 1, a + 1 + 2 * n);
    LL res = 0;
    LL cnt = 0;
    for (int i = 1; i <= 2 * n; i++) {
        int p = a[i].first, id = a[i].second;
        if (id == -1) {
            if (cnt >= k - 1)
                res = (res + fact[cnt] * infact[k - 1] % mod *
                                 infact[cnt - k + 1] % mod) %
                      mod;
            cnt++;
        } else
            cnt--;
    }
    cout << res << '\n';
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值