寒假思维训练计划day3

Day3(贪心 + 树状数组 + 分块 + 二分,2024-01-07) Problem - D2 - Codeforces

这是一道很综合的题,从想出来到写出来,收获满满。

题意:
给定两个长度为n的数组,可以对a数组进行操作:
选定l <= r, a[l], a[l + 1], .. a[r] := max(a[l], a[l + 1], ..., a[r]) 
使得最后的b==a
题解:大数据范围,O(n * n)会超时
先对b值相同的划分为一个块,记录每个值对应的索引数组(用于二分),贪心的从小值的块开始去更新,,为什么这样子贪心呢?首先是比它小的元素不会对他有影响,它的变化不会影响到比他大的块,用树状数组标记已经处理过的块,用前缀和去check一下它和边界位置的前缀和如果不为0就是有更小的块已经处理过了,如果是和自己相等的块则一定会被二分出来,一直重复这个步骤,算法完成。

C++代码,代码比较长,建议只看solve()部分。

#include  
#define lowbit(x) (x&-x)
#define int long long 
#define ff first 
#define ss second 
#define pb push_back 
#define ins insert 
using namespace std; 
using PII = pair; 
const int N = 1e6 + 5, inf = 0x3f3f3f3f; 
int n, m, cnt; 
int a[N], b[N], p[N], f[N][20], tr[N]; 
void add(int x, int c) {
    for(int i = x; i <= cnt; i += lowbit(i)) tr[i] += c; 
} 
int sum(int x) {
    int res = 0;
    for(int i = x; i; i -= lowbit(i)) res += tr[i]; 
    return res; 
}
void solve(int o) {
    cnt = 0;
    cin >> n; 
    vector block[n + 2]; 
    vector point[n + 2]; 
    tr[0] = 0;
    for(int i = 1; i <= n; i ++ ) {
        cin >> a[i]; 
        point[a[i]].pb(i);
        tr[i] = 0;
    }
    for(int i = 1; i <= n; i ++ ) 
        for(int j = 0; j <= 19; j ++ )
            f[i][j] = 0; 
    for(int i = 1; i <= n; i ++ ) f[i][0] = a[i];
    for(int j = 1; j <= 19; j ++ )     
        for(int i = 1; i <= n; i ++ ) 
            if(i + (1 << (j - 1)) - 1 <= n) 
                f[i][j] = max(f[i][j - 1], f[i + (1ll << (j - 1))][j - 1]);
    for(int i = 1; i <= n; i ++ ) cin >> b[i];
    for(int i = 1; i <= n; i ++ ) {
        int j = i, mx = -inf;
        while(j + 1 <= n && b[j + 1] == b[i]) ++ j; 
        ++ cnt; 
        for(int k = i; k <= j; k ++ ) p[k] = cnt; // 映射到块
        block[b[i]].pb({i, j});     
        i = j;
    } 
    bool flag = 1; 
    for(int bv = 1; bv <= n; bv ++ ) {
        if(!block[bv].size()) continue;
        for(auto t : block[bv]) {
            int l_ = t.ff, r_ = t.ss, mx = 0, i1, i2;
            if(!point[b[l_]].size()) {
                flag = 0;
                break;
            }
            for(int i = l_; i <= r_; i ++ ) mx = max(mx, a[i]); 
            if(mx == b[l_]) {
                add(p[l_],1);
                continue; 
            }
            int l = 0, r = point[b[l_]].size() - 1; 
            while(l < r) {
                int mid = l + r + 1 >> 1; 
                if(point[b[l_]][mid] <= l_) l = mid;
                else r = mid - 1; 
            }i1 = point[b[l_]][l];
            l = 0, r = point[b[l_]].size() - 1; 
            while(l < r) {
                int mid = l + r >> 1; 
                if(point[b[l_]][mid] >= r_) r = mid;
                else l = mid + 1;
            }i2 = point[b[l_]][l];
            bool f1 = 0, f2 = 0;
            if(i1 <= l_) {
                l = i1, r = r_;
                int ck = sum(p[r]) - sum(p[l]), len = r - l + 1;
                int k = log(len) / log(2);
                int mx = max(f[l][k], f[r - (1 << k) + 1][k]);
                if(!ck && mx == b[l_]) {
                    f1 = 1; 
                    add(p[l_], 1);
                }
            }
            if(!f1 && i2 >= r) {
                l = l_, r = i2;
                int ck = sum(p[r]) - sum(p[l]), len = r - l + 1;
                int k = log(len) / log(2);
                int mx = max(f[l][k], f[r - (1 << k) + 1][k]);
                if(!ck && mx == b[l_]) {
                    f2 = 1; 
                    add(p[l_], 1);
                }
            }
            
            if(!f1 && !f2) {
                flag = 0;
                break;
            }
        }
        if(!flag) break;
    }
    if(flag) cout << "YES" << endl; 
    else cout << "NO" << endl; 
    
}
signed main() {
    ios::sync_with_stdio(false); 
    cin.tie(0); 
    cout.tie(0);
    int ts; 
    cin >> ts; 
    for(int o = 1; o <= ts; o ++ ) {
        solve(o);
    }   
    return 0; 
}

你可能感兴趣的:(算法)