CF_div3_905(D/E/G2)

D. In Love

原题链接:https://codeforces.com/contest/1883/problem/D

题目大意:

维护一个区间的集合,然后每次加减区间后,判断集合中是否存在两个区间不相交。

思路:

方法1: 由于区间散布很大,所以常规的离线做法+离散化区间,这种区间问题容易想到差分,每次加区间就在对应位置上加 \(1\),然后每次查询数值最大的位置是否为当前集合内区间的个数,如果是那么在该点所有区间相交,否则存在一对区间不相交,差分虽然为 \(O(1)\),但查询为 \(O(n)\),但多次查询明显不行,所以利用线段树去实现,具体为查询区间 \(max\),且可区间修改的线段树,然后每次判断全部范围内的最大值,再结合上面说过的判断条件就做出来了。

还有因为 \(q\) 为 \(1e5\),所以端点至多为 \(2e5\),要注意 \(N\) 的取值!

方法2(官解):维护一下左端点(l)最大的线段和右端点(r)最小的线段,若他俩没交点显然存在,否则所有线段在 \(r\) ~ \(l\) 间相交。 这里只用两个 \(mutilset\) 即可。

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

代码(线段树):

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

#define TII tuple<int, int, int>
#define all(x) x.begin(), x.end()

typedef long long ll;
typedef pair<int, int> PII;

const int N = 2e5 + 10, M = 2e5 + 10, K = 20, mod = 998244353;
const int INF = 0x3f3f3f3f;
const ll INF_L = 1e15;

int n, m;

struct Node{
    int l, r;
    int mx;
    int lazy;
}tr[4 * N];

void push_up(Node &U, Node L, Node R){
    U.mx = max(L.mx, R.mx);
}

void push_up(int u){
    push_up(tr[u], tr[u << 1], tr[u << 1 | 1]);
}

void build(int u, int l, int r){
    if(l == r) tr[u] = {l, r, 0, 0};
    else{
        tr[u] = {l, r, 0, 0};
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r); 
    }
}

void push_down(int u){
    auto &U = tr[u]; 
    if(U.lazy == 0) return;
    auto &L = tr[u << 1], &R = tr[u << 1 | 1];
    L.mx += U.lazy, L.lazy += U.lazy;
    R.mx += U.lazy, R.lazy += U.lazy;
    U.lazy = 0;
}

void modify(int u, int l, int r, int c){
    if(tr[u].l >= l && tr[u].r <= r){
        tr[u].mx += c;
        tr[u].lazy += c;
    }else{
        push_down(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if(l <= mid) modify(u << 1, l, r, c);
        if(r > mid) modify(u << 1 | 1, l, r, c);
        push_up(u);
    }
}

void solve()
{
    cin >> m;
    vector<PII> rng(m);
    vector<int> num, op(m);
    string s;
    for(int i = 0; i < m; i ++){
        cin >> s;
        op[i] = (s[0] == '+');
        auto &[l, r] = rng[i];
        cin >> l >> r;
        num.push_back(l), num.push_back(r);
    }
    sort(all(num));
    num.erase(unique(all(num)), num.end());
    for(int i = 0; i < m; i ++){
        auto &[l, r] = rng[i];
        l = lower_bound(all(num), l) - num.begin() + 1;
        r = lower_bound(all(num), r) - num.begin() + 1;
    }
    n = num.size() + 5;
    build(1, 1, n);
    int c = 0;
    for(int i = 0; i < m; i ++){
        auto [l, r] = rng[i];
        if(op[i]) modify(1, l, r, 1), c ++;
        else modify(1, l, r, -1), c --;
        if(c > tr[1].mx) cout << "YES\n";
        else cout << "NO\n";
    }
}

int main()
{
    cin.tie(0)->sync_with_stdio(false);
    cout.tie(0);

    int t = 1;
    // cin >> t;

    while (t--) solve();

    return 0;
}

E. Look Back

原题链接:https://codeforces.com/contest/1883/problem/E

题目大意:

给定 \(n\) 个数的序列,每次可以任选一个位置的数乘 \(2\),求使得序列不递减的最小操作次。

思路:

一开始直接暴力,喜提 \(wa\),后面没意识到最后一个数可以被构造乘好多 \(2\),进而爆 \(long long\),喜提 \(10\) 几发 \(wa\),(一个可爆 long long 的构造: \(1e9,1e9 - 1,1e9 - 2 ... 1e9 - 1e5 + 1\))。

用\(b[i]\)去表示第\(i\)个位置要乘以\(2\)的个数,这里要分两种情况如果:当\(a[i] >= a[i + 1]\) 的那么我们先令 \(b[i + 1] == b[i]\),除此之外,还要给\(b[i + 1]\) 加上一个 \(t\),使\(a[i + 1] * 2 ^ t ≥ a[i]\),令 \(p = (a[i] + a[i + 1] - 1) / a[i + 1]\),令 \(p\) 的最高位二进制\(1\)所在位数为\(lg(p)\),且显然我们可以给\(a[i + 1]\)乘的数的二进制位只能有一位 \(1\),所以当 \(p\) 只有一位\(1\)时,\(t = lg(p)\),否则\(t = lg(p) + 1\);当\(a[i] < a[i + 1]\)时,令\(p = a[i + 1] / a[i]\),当\(b[i] <= lg(p)\) 时,显然令 \(b[i + 1]=0\)即可,因为\(p >= 2^{b[i]}\),否则我们只需要使得\(p\)的最高位\(1\)移到\(b[i]\)即可。

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

代码:

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

#define TII tuple<int, int, int>
#define all(x) x.begin(), x.end()

typedef long long ll;
typedef pair<int, int> PII;

const int N = 2e5 + 10, M = 2e5 + 10, K = 20, mod = 998244353;
const int INF = 0x3f3f3f3f;
const ll INF_L = 1e15;

int n, m;

void solve()
{
    cin >> n;
    vector<ll> a(n + 1);
    for(int i = 1; i <= n; i ++) cin >> a[i];
    vector<int> b(n + 1, 0);
    for(int i = 1; i < n; i ++){
        if(a[i] >= a[i + 1]){
            b[i + 1] = b[i];
            ll p = (a[i] + a[i + 1] - 1) / a[i + 1];
            ll s = p, c = 0;
            while(s){
                s -= s & -s;
                c ++;
            }
            int t = __lg(p);
            if(c > 1) t ++;
            b[i + 1] += t;
        }else{
            ll p = a[i + 1] / a[i];
            int t = __lg(p);
            b[i + 1] = max(0, b[i] - t);
        }
    }
    cout << accumulate(b.begin(), b.end(), 0ll) << endl;
    // for(int i = 1; i <= n; i ++) cout << b[i] << ' ';
    // cout << endl;
}

int main()
{
    cin.tie(0)->sync_with_stdio(false);
    cout.tie(0);

    int t = 1;
    cin >> t;

    while (t--) solve();

    return 0;
}

G2. Dances (Hard Version)

题目大意:

给定一个长度为\(n-1\)的序列\(a\),长度为\(n\)的序列\(b\),构造序列\(c\),\(c[1]\)的取值可为\(1\) ~ \(m\),\(c[2\) ~ \(n] = a\),\(c和b\)均可重排,每次操作可以从\(c和b\)中各取一个值丢弃,最后各剩余\(k\)个数,且满足\(c[i]<b[i]\) \(i∈[1,k]\),求\(c[1]\)所有情况下,对应\(k\)值最大的情况下操作数的总和。

思路:

其实就是一个匹配问题,直接排序后从小到大匹配即可(每个去匹配小于它最大的),只不过我们一开始不能确定\(c[1]\),所以先匹配\(c[1]\)之外的部分,看最后\(b[]\)中能剩下的的最大值为多少,然后分情况讨论即可。

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

代码:

复制代码
#include <bits/stdc++.h>
using namespace std;

#define TII tuple<int, int, int>
#define all(x) x.begin(), x.end()

typedef long long ll;
typedef pair<int, int> PII;

const int N = 2e5 + 10, M = 2e5 + 10, K = 20, mod = 998244353;
const int INF = 0x3f3f3f3f;
const ll INF_L = 1e15;

int n, m;

void solve()
{
    cin >> n >> m;
    multiset<int> st;
    for(int i = 1; i < n; i ++){
        int x;
        cin >> x;
        st.insert(x);
    }
    vector<int> b(n);
    for(int i = 0; i < n; i ++) cin >> b[i];
    sort(b.begin(), b.end());
    ll res = 0, mx = 0;
    for(int i = 0; i < n; i ++){
        if(st.lower_bound(b[i]) != st.begin()){
            st.erase(-- st.lower_bound(b[i]));
        }else{
            res ++;
            mx = b[i];
        }
    }
    ll ans = res * m;
    if(m < mx) ans -= m;
    else ans -= (mx - 1);
    cout << ans << endl;
}

int main()
{
    cin.tie(0)->sync_with_stdio(false);
    cout.tie(0);

    int t = 1;
    cin >> t;

    while (t--) solve();

    return 0;
}