Contestant(alt). 开摆Rank 5778. Rating -11(+89 -100).

A. Beautiful Sequence

题意

给定一个序列,输出是否存在一个子序列,存在一个元素满足 \(a_i = i\)

思路

显然,我们只需遍历一遍序列,找出一个 \(a_i\),满足 \(a_i \leq i\) 即可。

时间复杂度:\(O(n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define fs first
#define sc second

const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f;

void init(){}

void solve() {
    int n;
    cin >> n;
    bool f = false;
    for(int i=1;i<=n;i++){
        int cur;
        cin >> cur;
        if(cur <= i) f = true;
    }
    cout << (f ? "YES\n" : "NO\n");
}

signed main(){
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    init();
    int t;
    cin >> t;
    while(t --) solve();
}

B. Candies

题意

对于一个数 \(x\),定义操作如下:

  1. \(x :=2x + 1\)

  2. \(x := 2x - 1\)

给定一个若干次操作后的数,判断初始值是否为 \(1\),并输出方案。

思路

首先,得到的数都是奇数,所以我们从这里下手即可。

我们判断 \(\frac{x-1}{2}\) 的奇偶性,若为奇数,那么 \(x := \frac{x-1}{2}\),否则 \(x := \frac{x + 1}{2}\)

可以断定,只要一开始 \(x\) 不是偶数,最后的结果一定是 \(1\)

时间复杂度:\(O(\log_2n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define fs first
#define sc second

const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f;

void init(){}

void solve() {
    int n;
    cin >> n;
    if(n % 2 == 0) {
        cout << -1 << '\n';
        return;
    }
    vector<int> ans;
    while(n > 1){
        if((n - 1) / 2 % 2 == 0) n = (n + 1) / 2, ans.emplace_back(1);
        else n = (n - 1) / 2, ans.emplace_back(2);
    }
    reverse(ans.begin(), ans.end());
    cout << ans.size() << '\n';
    for(auto e : ans) cout << e << ' ';
    cout << '\n';
}

signed main(){
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    init();
    int t;
    cin >> t;
    while(t --) solve();
}

你说我这脑子怎么就转不过来呢

C. Make It Permutation

题意

给定一个序列,定义操作为下面两者任选其一:

  1. 删除一个元素,代价为 \(c\)

  2. 插入一个元素,代价为 \(d\)

找出代价最小的一种方案,使最后的序列变为任意长度的排列,输出最小代价。

思路

贪心。

我们先记最后的答案为 \(cost\)

首先,我们不妨先把序列排个序,然后用 \(unique\) 剔除重复元素,毕竟重复元素是一定要删去的,我们记剔除这些元素的总代价为 \(rm\)

然后,我们从大到小遍历,处理相邻的两个数。对于这两个数,我们定义差值为 \(dist\),以及右边那个数的后面还有 \(cnt\) 个数字。那么,我们有两种处理:

  1. \(dist=1\),那么不用管之

  2. \(dist > 1\),我们需要比较一下删除这个元素和补上缺少的数的代价。

需要注意的是,如果我们决定删除这个数,那么后面的数都要一并删去。因为后面的数不删去的话,需要补全的数就更多了,而我们已经判定删除的代价更小。也就是说,删除后总代价更新为 \(rm + c (cnt + 1)\)

而补全数字的代价为 \(d(dist-1)\),也就是说,更新后的总价值为 \(cost+d(dist-1)\)

因此,我们比较 \(cost+d(dist-1)\)\(rm + c (cnt + 1)\) 即可。

需要注意的是,当第一个数不是 \(1\) 的时候,我们不能删完所有数,所以一定会出现补全的代价。

到这里并没有结束,你会发现样例过不去。

这里有个有趣的点,我们为何不直接删掉所有数,然后放一个 \(1\) 进去呢?我们将其和答案取最小值即可。

时间复杂度:\(O(n \log n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define fs first
#define sc second

const int N = 2e7 + 10, inf = 0x3f3f3f3f3f3f3f3f;

void init(){}

void solve() {
    int n, c, d;
    cin >> n >> c >> d;
    //反着做,看看删掉和补充哪个赚
    vector<int> a(n);
    for(int i=0;i<n;i++) cin >> a[i];
    sort(a.begin(), a.end());
    int rm = a.size(), sz = unique(a.begin(), a.end()) - a.begin();
    rm -= sz;
    if(a[0] != 1) a.insert(a.begin(), 0ll), sz ++;
    int cost = rm * c;
    for(int i=sz-1;i>0;i--){
        int dis = a[i] - a[i - 1] - 1;
        if(a[i - 1] != 0 && cost + dis * d > rm * c + c * (sz - i)){
            cost = rm * c + c * (sz - i);
        }else cost += dis * d;
    }
    cout << min(d + c * n, cost) << '\n';
}

signed main(){
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    init();
    int t = 1;
    cin >> t;
    while(t --) solve();
}

简单的贪心捏,就是我怎么老搞错(

D. Climbing the Tree

题意

定义一个蜗牛在爬树,每天上升 \(a\),下降 \(b\),当某一天距离树的顶部小于等于 \(a - 1\) 时,即可爬到顶。树的高度记为 \(h\)

定义询问为下者二选一:

  1. \(1\ a\ b \ n\),表示给定一组限制,在 \(a, b\) 的条件下在第 \(n\) 爬到顶。若限制不满足前面得到的范围,输出 \(0\);否则输出 \(1\),并更新 \(h\) 的可能范围;

  2. \(2\ a\ b\),判定这个条件下所需天数是否有唯一解,有的话输出,否则输出 \(-1\)

思路

首先,这是一道数学题。

我们先来考虑第一个:

如果它要在第 \(n\) 天到达,首先前提是第 \(n - 1\) 天不会到达,也就是说,\(h \geq (a - b)(n - 1) + b\)

其次,爬到顶的条件是距离树的顶部小于等于 \(a - 1\),也就是说,\(h \leq (a - b)(n - 1) + (a - 1)\)

所以,若原求得的区间和 \([(a - b)(n - 1) + b, (a - b)(n - 1) + (a - 1)]\) 有交集,那么更新区间为两者交集,否则输出 \(0\)

我们记得到的 \(h \in [lh, rh]\)

我们再来看看第二个:

首先,如果能一步登天,即 \(a - 1 \geq rh\),那么直接输出 \(1\) 即可。

否则,我们只需满足一个不等式:\((a - b)(n - 1) + b \leq h \leq (a - b)(n - 1) + (a - 1)\)

化简可得:

$ \[\begin{cases} n \leq \frac{h - b}{a - b} + 1 \\ n \geq \frac{h - (a - 1)}{a - b} + 1\end{cases}\]

$

那么,我们把 \(rh\) 带入第一个式子,\(lh\) 带入第二个式子,即可求出 \(n\) 的所有可能范围。

那么,我们判断一下左右端点取整后是否相等即可。注意,右端点向下取整,左端点向上取整,因为这样取整后得到的区间是在原区间内的最长区间。

时间复杂度:\(O(n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define fs first
#define sc second

const int N = 2e7 + 10, inf = 0x3f3f3f3f3f3f3f3f;

void init(){}

void solve() {
    int q;
    cin >> q;
    int lh = -1, rh = inf;
    while(q --){
        int tp;
        cin >> tp;
        if(tp == 1){
            int a, b, n;
            cin >> a >> b >> n;
            int now = (a - b) * (n - 1);
            int now_l = n == 1 ? 0 : now + b, now_r = n == 1 ? a - 1 : now + a - 1;
            if(a <= b || now_l > rh || now_r < lh) cout << 0 << ' ';
            else{
                cout << 1 << ' ';
                lh = max(lh, now_l), rh = min(rh, now_r);
            }
        }else{
            int a, b;
            cin >> a >> b;
            if(a > rh) {
                cout << 1 << ' ';
                continue;
            }
            int min_x = (int) ceil((double) (lh - (a - 1)) / (double) (a - b)) + 1, max_x = (int) floor((double) (rh - (b)) / (double) (a - b)) + 1;
            if(min_x != max_x) cout << -1 << ' ';
            else cout << min_x << ' ';
        }
    }
    cout << '\n';
}

signed main(){
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    init();
    int t = 1;
    cin >> t;
    while(t --) solve();
}

别用 C++ 64位交即可.