1800

Messager in MAC

出处:CF1935C(Div.2)

题目大意:给定 $a_1,\ldots a_n$ 和 $b_1,\ldots,b_n$ ,要选出 $p_1,\ldots,p_k$ ,所有的 $p_i$ 是独立的,要使 $\sum\limits_{i=1}^{k}a_{p_i}+\sum\limits_{i=1}^{k-1}|b_{p_i}-b_{p_{i+1}}| \leq l$ ,求 $k$ 的最大值,注意当 $k=1$ 时,上述式子的取值为 $a_i$ 。

数据范围: $1 \leq n \leq 2000, 1 \leq n^2 \leq 4 \cdot 10^6,1 \leq a_i,b_i \leq 10^9$

思路:易见对于同样的数,当 $b_1,\ldots,b_k$ 单调时,原式的值取得最小,即 $|b_{k}-b_1|$ 。

然后观察到这个数据范围,应该是 $O(N^2)$ 或者 $O(N^2 \log n)$ 的。

考虑枚举首尾两个元素,然后对中间的进行归并,但这样最差复杂度是 $O(n^3 \log n)$ ,不可行。

因此,考虑贪心地优化,首先按照 $b$ 排序,然后考虑滑动窗口的思路,枚举左端点逐步往右扩展,使用mset自动排序,然后弹出的之后也不可能用到,因为后续枚举的尾值肯定比这个大,能接受的 $a$ 只会小于等于这里能接受的 $a$ ,时间复杂度 $O(n^2 \log n)$ 。

这道题没有独立做出,说明贪心和ds功底比较差。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void solve() {
    int n, l;
    cin >> n >> l;
    vector<pii> a(n);
    rep(i, 0, n - 1) cin >> a[i].first >> a[i].second;
    sort(all(a), [&](const pii &x, const pii &y) { return x.second < y.second; });
    int ans = 0;
    rep(i, 0, n - 1) {
        multiset<int> s;
        ll cur = 0;
        rep(j, i, n - 1) {
            s.insert(a[j].first);
            cur += a[j].first;
            while (!s.empty() && cur + 1LL * a[j].second - a[i].second > l) {
                int tem = *s.rbegin();
                cur -= 1LL * tem;
                s.erase(--s.end());
            }
            ans = max(ans, sz(s));
        }
    }
    cout << ans << endl;
    return;
}

Unfair Game

出处:CF1955F(Div.3)

题目大意:Alice和Bob在对着一堆不超过4的数玩游戏,每次若它们的异或和为0则Bob获胜,反之Alice获胜,现在Eve负责每次删除一个数字(空的时候不会进行游戏),问Bob最多能赢几局?

数据范围: $0 \leq p_i \leq 200, 1 \leq i \leq 4$

思路:简单的贪心,思路这里不讲了,可以达到 $O(1)$ ,注意到 $4$ 是独立的,且 $1,2,3$ 必须同奇偶即可。

还有一种DP思路,就是先把4单独考虑,然后枚举状态 $dp_{i,j,k}$ ,这里 $i,j,k$ 分别是 $1 \sim 3$ 的个数,它们可以从 $dp_{i-1,j,k},dp_{i,j-1,k},dp_{i,j,k-1}$ 转移过来,于是有答案。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
void solve() {
    vi p(4);
    rep(i, 0, 3) cin >> p[i];
    int ans = 0;
    if (p[0] > 0 && p[1] > 0 && p[2] > 0) {
        sort(p.begin(), p.begin() + 3);
        int tem = 0;
        tem += p[0];
        tem += (p[1] - (p[1] - p[0]) % 2 - p[0]) / 2 + (p[2] - (p[2] - p[0]) % 2 - p[0]) / 2 + p[3] / 2;
        ans = max(ans, tem);
        tem = 0;
        if (p[0] % 2 == 1 && p[1] % 2 == 1 && p[2] % 2 == 1) tem++;
        tem += p[0] / 2 + p[1] / 2 + p[2] / 2 + p[3] / 2;
        ans = max(ans, tem);
    } else
        ans = p[0] / 2 + p[1] / 2 + p[2] / 2 + p[3] / 2;
    cout << ans << endl;
}

Magic Will Save the World

出处:CF1862F(Div.3)

题目大意:每秒可以产生 $w$ 的火能量和 $f$ 的水能量,每个怪物需要 $s_i$ 个火能量或者水能量来杀死(不能混用),问最少需要多少秒(假设杀死怪物不需要时间)。

数据范围: $1 \leq n \leq 100, 1 \leq w,f \leq 10^9, 1 \leq s_i \leq 10^4$

思路:首先考虑二分答案,然后观察到这好像是一个两个背包的模型,因此只需要让遍历的其中一个背包塞得最多,再考虑另一个背包塞不塞得下即可。

同时,这里上界的取法很有意思,可以尽量减少复杂度。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
void solve() {
    int w, f;
    cin >> w >> f;
    int n;
    cin >> n;
    vi a(n);
    rep(i, 0, n - 1) cin >> a[i];
    ll tot = 0;
    rep(i, 0, n - 1) tot += 1LL * a[i];
    int l = 1, r = tot / max(w, f) + 1, mid;
    int ans = 0;
    auto check = [&](int mid) -> bool {
        vector<bool> dp(min(1LL * tot, 1LL * mid * w) + 1, false);
        dp[0] = true;
        int m = sz(dp);
        rep(i, 0, n - 1) {
            if (a[i] > m - 1) continue;
            frep(j, m - 1, a[i]) dp[j] = dp[j] | dp[j - a[i]];
        }
        ll tem = 1LL * mid * f;
        ll tem2 = 0;
        frep(i, m - 1, 0) {
            if (dp[i]) {
                tem2 = i;
                break;
            }
        }
        return 1LL * tot - tem2 <= tem;
    };
    while (l <= r) {
        mid = (l + r) / 2;
        if (check(mid)) {
            r = mid - 1;
            ans = mid;
        } else
            l = mid + 1;
    }
    cout << ans << endl;
    return;
}

Inversion Value of a Permulation

出处: CF2145D(Edu)

题目大意: 给定一个长度为 $n$ 的排列,定义它的反转值为至少包含一个逆序对的子段的数量,请构造出反转值为 $k$ 的排列,或者报告这样的排列不存在。

数据范围: $2 \leq n \leq 30, 0 \leq k \leq \frac{n(n-1)}{2}$

思路:考虑正难则反,首先考虑构造没有逆序对的子段数量为 $\frac{n(n+1)}{2}-k$ ,然后观察到每段单调递增的子段的贡献是 $\frac{l(l+1)}{2}$ ,而所有单调递增的子段长度之和为 $n$ (只有一个元素也是),因此考虑采用可行式背包求解。

接着,再用原来的子段,贪心还原即可。

这题参考了题解。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void solve() {
    int n, k;
    cin >> n >> k;
    int nn = n;
    k = n * (n + 1) / 2 - k;
    vvi dp(n + 1, vi(n * (n + 1) / 2 + 1));
    dp[0][0] = 1;
    vi res;
    rep(i, 1, n) {
        rep(j, i, n) {
            rep(v, i * (i + 1) / 2, n * (n + 1) / 2) { dp[j][v] = dp[j][v] | dp[j - i][v - i * (i + 1) / 2]; }
        }
    }
    if (!dp[n][k]) {
        cout << 0 << endl;
        return;
    }
    while (k > 0) {
        rep(i, 1, n) {
            if (dp[n - i][k - i * (i + 1) / 2]) {
                rep(j, n - i + 1, n) res.push_back(j);
                n -= i, k -= i * (i + 1) / 2;
                break;
            }
        }
    }
    for (int& p : res) cout << p << ' ';
    cout << endl;
    return;
}

Andrey and Escape from Capygrad

出处:CF1859D(Div.2)

题目大意:有 $n$ 个传送门,每个传送门分布在 $[l_i,r_i]$ ,且可以传送到 $[a_i,b_i],l_i \leq a_i \leq b_i \leq r_i$ , 所有传送门都可以被任意次使用,现在给出 $q$ 个查询,请输出从 $x$ 出发,可以到达的最大坐标是多少。

数据范围: $1 \leq l_i \leq a_i \leq b_i \leq r_i \leq 10^9, 1 \leq n \leq 2 \cdot 10^5, 1 \leq q \leq 2 \cdot 10^5,1 \leq x \leq 10^9$

思路:首先考虑区间合并,这道题是LC56的典题,如果没有复习的可以去复习一下。

考察合并 $[l_i,b_i]$ ,即这之间的点可以任意到达右边界,为什么不考察 $(b_i,r_i]$ ?因为如果 $[l_i,b_i]$ 合并的结果到不了 $r_i$ ,那此时往回走是完全不划算的事情,反之自然会包括这一段,因此只需要考察前面的。

最后,用二分查找搜索当前查询在哪个区间即可。

这题参考了题解。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void solve() {
    int n, a, b, c, d, q;
    cin >> n;
    vector<pii> ma;
    rep(i, 0, n - 1) {
        cin >> a >> b >> c >> d;
        ma.emplace_back(a, d);
    }
    ranges::sort(ma);
    vector<pii> res;
    for (auto& p : ma) {
        if (!res.empty() && res.back().second >= p.first) {
            res.back().second = max(res.back().second, p.second);
        } else
            res.push_back(p);
    }
    cin >> q;
    rep(i, 0, q - 1) {
        cin >> a;
        auto x = ranges::upper_bound(res, make_pair(a, INT_MAX));
        if (x == res.begin())
            cout << a << ' ';
        else {
            if ((--x)->second >= a)
                cout << x->second << ' ';
            else
                cout << a << ' ';
        }
    }
    cout << endl;
    return;
}

Rating System

出处:CF1845D(Edu)

题目大意:现在正在开发评级系统,一开始的评级为 $0$ ,每次会变化 $a_i$ 的评级,对于固定整数 $k$ ,若已经达到过 $k$ ,则后续不会降到 $k$ 以下,现在要求确定 $k$ 的值,使得最后的值最大。

数据范围: $1 \leq n \leq 3 \cdot 10^5, -10^9 \leq a_i \leq 10^9, a_i \not ={0}$

思路:首先贪心地考虑,如果一段使得已经达到的评级降下来,那么它会被抵消成0,如果后续还有一段使得它降下来,那么这整段都要被抵消掉(由于前缀和的原因,可以思考一下)。

于是,为了使最后的值最多,需要抵消掉最多的部分,也就是总数减去最小子段和(注意这个子段和必须要是负数),然后再还原方案即可。

其实1600-1800的题,基本都是一个小trick/一个小思维就能转到学过的东西。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void solve() {
    int n;
    cin >> n;
    vi a(n);
    rep(i, 0, n - 1) cin >> a[i];
    vl pre(n);
    pre[0] = a[0];
    rep(i, 1, n - 1) pre[i] = pre[i - 1] + 1LL * a[i];
    vl dp(n, LLONG_MAX / 3);
    dp[0] = a[0];
    ll ans = LLONG_MAX;
    int idx = -1;
    rep(i, 1, n - 1) {
        dp[i] = min(dp[i], 1LL * a[i]);
        dp[i] = min(dp[i], dp[i - 1] + 1LL * a[i]);
    }
    rep(i, 0, n - 1) {
        if (dp[i] < ans) {
            ans = dp[i];
            idx = i;
        }
    }
    if (ans >= 0) {
        cout << 0 << endl;
        return;
    }
    int cur = idx;
    while (cur >= 0) {
        if (dp[cur] == 1LL * a[cur]) {
            if (cur != 0)
                cout << pre[cur - 1] << endl;
            else
                cout << 0 << endl;
            return;
        }
        cur--;
    }
    return;
}

Colored Balls

出处:CF1954D(Edu)

题目大意:给定 $n$ 个颜色,每个颜色有 $a_i$ 个球,定义一个颜色组的值为,这些颜色的球能分配到的最小组数,每组最多包含两个球,且这两个球不能是同一个颜色的,现在要求求出所有 $2^n$ 个颜色组的值总和,由于结果太大,需要模998244353。

数据范围: $1 \leq n \leq 5000, \sum\limits_{i=1}^{n}a_i \leq 5000$

思路:这里注意到,所有 $a_i$ 的值之和不超过5000,所以大概率是在这里做文章。

接着,看到这个条件非常明显是绝对众数,那么只需要枚举是绝对众数的数,以及它不作为绝对众数的情况。

考虑先排序再递推,记录之前子集能构成的和及其数量,计数如果当前颜色被选了,它作为绝对众数和非绝对众数的情况,时间复杂度 $O(n \cdot 5000)$ 。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const int MOD = 998244353;
void solve() {
    int n;
    cin >> n;
    vi a(n);
    rep(i, 0, n - 1) cin >> a[i];
    ranges::sort(a);
    ll ans = 0;
    vl dp(5001, 0);
    dp[0] = 1;
    rep(i, 0, n - 1) {
        rep(j, 0, a[i]) {
            ans += 1LL * dp[j] * a[i] % MOD;
            ans %= MOD;
        }
        rep(j, a[i] + 1, 5000) {
            ans += 1LL * (j + a[i] + 1) / 2 * dp[j] % MOD;
            ans %= MOD;
        }
        auto ndp = dp;
        rep(j, 0, 5000 - a[i]) {
            dp[j + a[i]] += ndp[j] % MOD;
            dp[j + a[i]] %= MOD;
        }
    }
    cout << ans << endl;
    return;
}
本博客已稳定运行
发表了47篇文章 · 总计305.52k字
使用 Hugo 构建
主题 StackJimmy 设计