日常刷题2025-2-14

日常刷题2025-2-14

小鸡的排列构造的checker

https://ac.nowcoder.com/acm/contest/95338/I

题目大意

给你一个排列,给你一系列操作 \(l_i\) \(r_i\) \(c_i\),将 \(li\) \(ri\) 范围的数字排序,原来位置 \(c_i\) 处的数字移动到了哪里。

思路:树状数组+离线

本题预期的做法是使用离线将查询排序的技巧,使用树状数组进行维护。

我们举一个例子说明该做法的思路,例如输入的数组 ppp 为 [1,3,2,4,5],查询分别为 [1,5,2] 和 [2,5,4],我们发现 [1,5,2]查询相当于在问: [1,5]区间里有多少个小于等于 \(p_2\) 的数字?(因为这些数的个数能得出排序后 \(p2\) 的位置)。

于是我们可以维护一个初始全 0 的树状数组,按照 \(p_i\) 从小到大给树状数组的对应位置 +1,也就是在上述例子中,树状数组维护的原数组依次是:

状态1:[1,0,0,0,0] 状态2:[1,0,1,0,0] 状态3:[1,1,1,0,0] 状态4:[1,1,1,1,0] 状态5:[1,1,1,1,1]

这样询问 [1,5,2] 其实是在对状态2的数组求 [1,5] 区间的和,使用树状数组可以做到;询问 [2,5,4] 其实是在对状态4的数组求 [2,5] 区间的和,使用树状数组可以做到。

因此,像上述过程一样,把所有 [l,r,c] 询问按照 p[c] 从小到大排序,树状数组维护上述过程即可,复杂度 O(nlog⁡n)。

代码

#include <bits/stdc++.h>

using u64 = unsigned long long;
using i64 = long long;
typedef std::pair<int, int> pii;
const int INF = 0x3f3f3f3f;
const int mod = 998244353;
const long long LINF = 1e18;

void solve(){
    int n, m;
    std::cin >> n >> m;

    std::vector<int> p(n+1), pos(n+1);
    for (int i = 1; i <= n; i++){
        std::cin >> p[i];
        pos[p[i]] = i;
    }

    std::vector q(n+1, std::vector<int>());
    std::vector<int> l(m), r(m), c(m);
    for (int i = 0; i < m; i++){
        std::cin >> l[i] >> r[i] >> c[i];
        q[p[c[i]]].push_back(i);
    }

    std::vector<int> bit(n+1);
    auto add = [&](int i)->void{
        for ( ; i <= n; i += i & -i) bit[i] += 1;
    };

    auto sum = [&](int i)->int{
        int res = 0;
        for ( ; i; i -= i & -i){
            res += bit[i];
        }
        return res;
    };

    std::vector<int> ans(m);
    for (int i = 1; i <= n; i++){
        for (auto j : q[i]) ans[j] = sum(r[j]) - sum(l[j]-1) + l[j];
        add(pos[i]); 
    }

    for (auto e : ans) std::cout << e << '\n';
}

signed main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout<<std::setiosflags(std::ios::fixed)<<std::setprecision(15);

    int t = 1, i;
    std::cin >> t; 
    for (i = 0; i < t; i++){
        solve();
    }

    return 0;
}

好伙计猜拳

https://ac.nowcoder.com/acm/contest/95338/B

思路:最长上升子序列

https://www.bilibili.com/video/BV1yhKVeDEJE/?spm_id_from=333.337.search-card.all.click&vd_source=4a339d299e165d8fe38b9926c5240eae

评述

有两种操作,每种操作的代价不同,很容易让人以为是贪心,但是思考贪心要考虑的因素有非常多,此时可以想一想是不是DP,发现题目是无后效性的,所以可以DP。

无后效性:已经求解的子问题,不会再受到后续决策的影响。

代码

#include <bits/stdc++.h>

using u64 = unsigned long long;
using i64 = long long;
typedef std::pair<int, int> pii;
const int INF = 0x3f3f3f3f;
const int mod = 998244353;
const long long LINF = 1e18;

void solve(){
    i64 n, c1, c2;
    std::cin >> n >> c1 >> c2;
    std::vector<i64> a(n+1), b(n+1);
    std::vector<std::vector<i64>> dp(n+1, std::vector<i64>(2, 1e18));
    for (int i = 1; i <= n; i++) std::cin >> a[i] >> b[i];
    dp[0][0] = 0;
    i64 ans = 1e18;
    for (int i = 1; i <= n; i++){
        for (int j = 0; j <= i - 1; j++){
            if (a[j] <= a[i] && b[j] <= b[i]){
                dp[i][0] = std::min(dp[i][0], dp[j][0] + c1*(i-j-1));
            }
            if (b[j] <= a[i] && a[j] <= b[i]){
                dp[i][0] = std::min(dp[i][0], dp[j][1] + c1*(i-j-1));
            }
        }
        for (int j = 0; j <= i - 1; j++){
            if (a[j] <= b[i] && b[j] <= a[i]){
                dp[i][1] = std::min(dp[i][1], dp[j][0] + c1*(i-j-1));
            }
            if (b[j] <= b[i] && a[j] <= a[i]){
                dp[i][1] = std::min(dp[i][1], dp[j][1] + c1*(i-j-1));
            }
        }
        dp[i][1] += c2;
        ans = std::min(ans, std::min(dp[i][0], dp[i][1])+c1*(n-i));
    }

    std::cout << ans << '\n';
    
}

signed main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout<<std::setiosflags(std::ios::fixed)<<std::setprecision(15);

    int t = 1, i;
    std::cin >> t; 
    for (i = 0; i < t; i++){
        solve();
    }

    return 0;
}
posted @ 2025-02-14 15:17  califeee  阅读(28)  评论(0)    收藏  举报