cf1854b - Earn or Unlock

Problem - D - Codeforces

Better experience

1. Description

桌子上有 n n n 张卡牌按顺序从上到下摆放 , 初始时第一张已经解锁 , 其他的未解锁 , 每张卡牌上有一个值 a i a_i ai .

游戏按轮次进行. 每一轮 , 你需要选定一张解锁的卡牌 , 其值为 v v v , 并选择如下操作之一进行 :

  1. 解锁前 v v v未解锁的卡牌
  2. 获得 v v v 点数

每一轮结束后所选择的卡牌将会被丢弃.

游戏结束当且仅当无可选择的卡牌.

问可获得的最大点数.

1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^5 1n105 , 0 ≤ a i ≤ n 0 \leq a_i \leq n 0ain

2. Solution

bitset 优化背包, 复杂度 O ( n 2 / w ) O(n^2/w) O(n2/w)

这类直接操作有着后效性的题的入手点 : dp .

考虑维护什么状态可以消除后效性 , 得到朴素 dp 公式 :

dp[i][j] 表示考虑到第 i i i 张卡片 , 且前 j j j 张卡片被解锁时的最大分数.

显然 i i i 那一维可以删掉 , 这样就是一个 0/1 背包的形态.

但复杂度肯定不行 , 考虑优化 :

  • 单调队列 x
  • 斜率 x
  • 四边形不等式 x
  • 线段树 x

上述常见优化都不太行 , 因为枚举中的 i i i , j j j 都是必须量 , 无法减少 , 且转移方程非区间

而上述优化主要是通过维护额外信息来减少在区间上的枚举量.

这里需要一点灵感.

注意到实际上要么解锁 v v v 张卡片 , 要么获得 v v v 点数 , 这两个数量相等

这说明在固定 i i i 下 , 解锁卡片的数量加上获得的点数应该是一个定值 , 这个定值就是前 i i i 张卡片的值的和

进一步 , 当游戏结束后 ( i = n + 1 i=n+1 i=n+1) 如果知道了解锁卡片的数量 , 那么就可以算出获得的点数

所以得到了一个更简化的 dp 方程 :

dp[j] 表示 j j j 张卡片被解锁的情况是否存在.

复杂度并没有直观的下降 , 但有一点不同了 , dp 的值现在是 bool .

所以可以使用 bitset 去优化背包的转移.

最后 , 如果假设解锁卡片的数量为 k k k , 那么答案就是

∑ i = 1 min ⁡ ( k , n ) a i − k + 1 \sum_{i=1}^{\min(k,n)}a_i - k + 1 i=1min(k,n)aik+1

3. Code

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

std::mt19937 rng(std::random_device{}());
typedef long double ld;
typedef long long ll;
typedef unsigned long long ull;
typedef const int& cint;
typedef const ll& cll;
typedef pair<int, int> pii;
typedef pair<int, ll> pil;

#define ls (loc<<1)
#define rs ((loc<<1)|1)

const int mod1 = 1e9+7;
const int mod2 = 998244353;
const int inf_int = 0x7fffffff;
const int hf_int = 0x3f3f3f3f;
const ll inf_ll = 0x7fffffffffffffff;
const double ept = 1e-9;

int n;
int a[100100];
ll b[100100];
bitset<200001> v;

void solve(cint T) {
    cin >> n;
    for(int i=1; i<=n; i++) { cin >> a[i]; b[i] = a[i] + b[i-1]; }
    v[1] = 1;
    ll mx = 1;
    ll ans = 0;
    for(int i=1; i<=n && i<=mx; i++) {
        v |= v << a[i];
        mx += a[i];
        if(v[i]) {
            v[i] = 0;
            ans = max(ans, b[min(n,i)] - i + 1);
        }
    }
    for(int i=n+1; i<=n*2; i++) if(v[i]) {
        ans = max(ans, b[min(n,i)] - i + 1);
    }
    cout << ans << '\n';
}

int main() {
    //freopen("1.in", "r", stdin);
    //cout.flags(ios::fixed); cout.precision(8);
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int T_=1;
    //std::cin >> T_;
    for(int _T=1; _T<=T_; _T++)
        solve(_T);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值