CF1602A~F

A - Two Subsequences(语言基础)

最小字符+剩余部分

B - Divine Array (暴力)

看题解操作次数上界是\(O(\log n)\)的,但是\(O(n^2)\)能过。直接暴力。

C - Array Elimination(思维)

如果要把某一位全部减为0,那么就是把这一位为1的全部与起来。但是如果要把全部位都减掉,意味\(k\)很关键,每一位的1的个数必须是\(k\)的倍数。因此答案就是每一位1的个数的\(\gcd\)的所有因子。

D - Frog Traveler(set,bfs)

如果\(a\)能跳到\(b\)并从\(b\)滑倒\(c\),那么\(a\)\(c\)连一条边,意味着\(a\)能一次跳到达\(c\)。可以发现每个\(a\)对应都是一段连续的区间。因此直接用set维护未访问的结点,然后从\(n\)开始bfs,访问过的点直接从set中删除。这个操作也能用并查集实现。时间复杂度\(O(n \log n)\)

#include <bits/stdc++.h>

#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define mp make_pair
#define seteps(N) fixed << setprecision(N) 
typedef long long ll;

using namespace std;
/*-----------------------------------------------------------------*/

ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f

const int N = 3e5 + 10;
const double eps = 1e-5;

int d[N];
set<int> id;
int a[N], b[N];
typedef pair<int, int> PII;
PII range[N];
int fa[N];

int main() {
    IOS;
    memset(fa, -1, sizeof fa);
    memset(d, -1, sizeof d);
    int n;
    cin >> n;
    d[n] = 0;
    id.insert(0);
    for(int i = 1; i<= n; i++) {
        id.insert(i);
        cin >> a[i];
    }
    for(int i = 1; i <= n; i++) {
        cin >> b[i];
    }
    for(int i = 1; i <= n; i++) {
        int tar = i + b[i];
        range[i] = {tar - a[tar], tar};
    }
    d[n] = 0;
    queue<int> q;
    q.push(n);
    id.erase(n);
    while(!q.empty()) {
        int cur = q.front();
        q.pop();
        int l = range[cur].first, r = range[cur].second;
        auto tar = id.lower_bound(l);
        while(tar != id.end() && *tar <= r) {
            d[*tar] = d[cur] + 1;
            fa[*tar] = cur;
            q.push(*tar);
            int nt = *tar + 1;
            id.erase(tar);
            tar = id.lower_bound(nt);
            // cout << *tar << " ";
        }
        // cout << endl;
    }
    if(d[0] == -1) cout << -1 << endl;
    else {
        int cur = 0;
        vector<int> ans;
        while(cur != -1) {
            ans.push_back(cur);
            cur = fa[cur];
        }
        ans.pop_back();
        reverse(ans.begin(), ans.end());
        cout << d[0] << endl;
        for(int i = 0; i < ans.size(); i++) cout << ans[i] << " ";
        cout << endl;
    } 
}

E - Optimal Insertion (贪心,线段树)

首先对\(b\)排序,这样肯定是最优的。然后\(b\)插入\(a\)还是\(a\)插入\(b\)其实没区别,于是假设是\(b\)插入\(a\)\(p_i\)代表\(b_i\)放在了\(a_i\)\(a_{i+1}\)之间;对于放在\(a\)结尾的\(b_i\),令\(p_i=n+1\)

答案除了\(a\)本身之外,还有\(b\)插入带来的。\(a\)的很好求,主要是求\(b\)的贡献。贪心地考虑一下,就是从左往右找到第一个产生逆序对最少的位置,插入\(b_i\),要保证\(p_i \le p_{i+1}\),直到插完为止。

因此可以用线段树维护对于\(b_i\)放在每个位置(\(p\))产生的逆序对,整个区间中值最小的位置就是所要放\(b_i\)的位置。维护的方法见代码,同时可以发现,由于\(b_i\)是递增的,这个最小的位置也是递增的,同时保证了\(b_i\)产生逆序对最少以及\(p_i \le p_{i+1}\)(这样\(b_i\)之间也不会产生逆序对)。

#include <bits/stdc++.h>

#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define mp make_pair
#define seteps(N) fixed << setprecision(N) 
typedef long long ll;

using namespace std;
/*-----------------------------------------------------------------*/

ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f

const int N = 1e6 + 10;
const double eps = 1e-5;

int mi[N << 2], lazy[N << 2];

void pushdown(int rt) {
    if(lazy[rt]) {
        lazy[rt << 1] += lazy[rt];
        lazy[rt << 1 | 1] += lazy[rt];
        mi[rt << 1] += lazy[rt];
        mi[rt << 1 | 1] += lazy[rt];
        lazy[rt] = 0;
    }
}


void build(int l, int r, int rt) {
    if(l == r) {
        mi[rt] = lazy[rt] = 0;
        return ;
    }
    int mid = (l + r) / 2;
    build(l, mid, rt << 1);
    build(mid + 1, r, rt << 1 | 1);
    mi[rt] = lazy[rt] = 0;
}



void upd(int l, int r, int L, int R, int val, int rt) {
    if(l >= L && r <= R) {
        mi[rt] += val;
        lazy[rt] += val;
        return ;
    }
    int mid = (l + r) / 2;
    pushdown(rt);
    if(L <= mid) upd(l, mid, L, R, val, rt << 1);
    if(R > mid) upd(mid + 1, r, L, R, val, rt << 1 | 1);
    mi[rt] = min(mi[rt << 1], mi[rt << 1 | 1]);
}

int a[N], b[N];
vector<int> pos[N << 1];
int cnt[N << 1];
int tarr[N << 1];
int n, m, mx;

int lowbit(int x) {
    return x&-x;
}

void add(int p, int val) {
    while(p <= mx) {
        tarr[p] += val;
        p += lowbit(p);
    }
}

int sum(int p) {
    int res = 0 ;
    while(p) {
        res += tarr[p];
        p -= lowbit(p);
    }
    return res;
}

int main() {
    IOS;
    int t;
    cin >> t;
    while(t--) {
        vector<int> num;
        cin >> n >> m;
        build(1, n + 1, 1);
        for(int i = 1; i <= n; i++) cin >> a[i], num.push_back(a[i]);
        for(int i = 1; i <= m; i++) cin >> b[i], num.push_back(b[i]);
        sort(num.begin(), num.end());
        num.erase(unique(num.begin(), num.end()), num.end());
        mx = num.size();
        for(int i = 1; i <= mx; i++) pos[i].clear(), tarr[i] = 0, cnt[i] = 0;
        // 离散化
        for(int i = 1; i <= n; i++) a[i] = lower_bound(num.begin(), num.end(), a[i]) - num.begin() + 1;
        for(int i = 1; i <= m; i++) b[i] = lower_bound(num.begin(), num.end(), b[i]) - num.begin() + 1; 
        ll ans = 0;
        for(int i = 1; i <= n; i++) pos[a[i]].push_back(i), add(a[i], 1), ans += sum(mx) - sum(a[i]), upd(1, n + 1, i + 1, n + 1, 1, 1); // pos是a的值对应的位置,用于优化
        for(int i = 1; i <= m; i++) cnt[b[i]]++; // 统计b值的个数,由于有相同的值,所以用这种方法。
        for(int i = 1; i <= mx; i++) { // 枚举b
            for(int p : pos[i]) upd(1, n + 1, p + 1, n + 1, -1, 1);
            for(int p : pos[i - 1]) upd(1, n + 1, 1, p, 1, 1);
            ans += 1ll * cnt[i] * mi[1];
        }
        cout << ans << endl;
    }
}

F - Difficult Mountain(dp,优化)

分析一下,有一下几种情况:

  1. \(s <p\),不统计
  2. \(a \le p\)\(s \ge p\),直接统计,不影响结果
  3. \(a>p\)\(s\ge p\),单独讨论。

因此只需计算怎么排列情况3的人,使得答案最大。

假设最优情况是选择了集合\(S_i=\{a_i,s_i\}\),其中最大的\(a\)\(a_i\)。那么对于集合中所有\(s \ge a_i\)的人,它们排在\(i\)之后是最优的,而\(s < a_i\)的人必须排在\(i\)之前。排在\(i\)前面存在某个\(j\),有\(a_j\le a_i\),而\(j\)是排在\(i\)前面元素的集合\(S_j=\{a_j,s_j\}\sub S_i\),中\(a\)最大的。\(S_i-S_j\)中剩下这些人都是满足\(s \ge a_i\)\(a\ge a_j\),排在\(i\)的后面。显然这个满足最优子结构。

因此先对\(a\)排序,\(f(i)\)代表\(S_i\)的大小,即\(f(i)=|S_i|\)。有如下转移方程

\[f(i)=\max_{j=0}^{i-1}(f(j)+cal(j+1,i-1,i))[s_i\ge a_j] \]

其中

\[cal(l,r,p)=\sum\limits_{i=l}^{r}[s_i\ge a_p] \]

最后答案就是\(\max(f(i))\)加上情况2。

\(sum(l,p)=\sum\limits_{i=0}^{l}[s_i\ge a_p]\),可得

\[f(i)-sum(i-1,i)=\max_{j=0}^{a_j\le s_i}(f(j)-sum(j,i)) \]

用线段树维护更新\(\max\limits_{j=0}^{a_j\le s_i}(f(j)-sum(j,i))\),使用和E题类似的方法优化即可。

#include <bits/stdc++.h>

#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define mp make_pair
#define seteps(N) fixed << setprecision(N) 
typedef long long ll;

using namespace std;
/*-----------------------------------------------------------------*/

ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f

const int N = 1e6 + 10;
const double eps = 1e-5;
typedef pair<int, int> PII;

int s[N], a[N];
PII arr[N];
bool ok[N];
vector<int> pos[N];

int mx[N << 2], lazy[N << 2];
void pushdown(int rt) {
    if(lazy[rt]) {
        lazy[rt << 1] += lazy[rt];
        lazy[rt << 1 | 1] += lazy[rt];
        mx[rt << 1] += lazy[rt];
        mx[rt << 1 | 1] += lazy[rt];
        lazy[rt] = 0;
    }
}

void upd(int l, int r, int L, int R, int val, int rt) {
    if(l >= L && r <= R) {
        mx[rt] += val;
        lazy[rt] += val;
        return ;
    }
    pushdown(rt);
    int mid = (l + r) / 2;
    if(mid >= L) upd(l, mid, L, R, val, rt << 1);
    if(mid < R) upd(mid + 1, r, L, R, val, rt << 1 | 1);
    mx[rt] = max(mx[rt << 1], mx[rt << 1 | 1]); 
}

int que(int l, int r, int L, int R, int rt) {
    if(l >= L && r <= R) {
        return mx[rt];
    }
    pushdown(rt);
    int res = -INF;
    int mid = (l + r) / 2;
    if(mid >= L) res = max(res, que(l, mid, L, R, rt << 1));
    if(mid < R) res = max(res, que(mid + 1, r, L, R, rt << 1 | 1));
    mx[rt] = max(mx[rt << 1], mx[rt << 1 | 1]); 
    return res;
}

int dp[N];

bool cmp(const PII& a, const PII &b) {
    return a.first < b.first;
}

int main() {
    IOS;
    vector<int> num;
    int n, d;
    cin >> n >> d;
    num.push_back(d);
    for(int i = 1; i <= n; i++) {
        cin >> s[i] >> a[i];
        num.push_back(s[i]);
        num.push_back(a[i]);
    }
    sort(num.begin(), num.end());
    num.erase(unique(num.begin(), num.end()), num.end());
    d = lower_bound(num.begin(), num.end(), d) - num.begin() + 1;
    for(int i = 1; i <= n; i++) {
        s[i] = lower_bound(num.begin(), num.end(), s[i]) - num.begin() + 1;
        a[i] = lower_bound(num.begin(), num.end(), a[i]) - num.begin() + 1;
    }
    int ans = 0;
    for(int i = 1; i <= n; i++) {
        if(a[i] <= d && s[i] >= d) ans++;
        if(a[i] > d && s[i] >= d) ok[i] = true; 
    }
    int m = 0;
    for(int i = 1; i <= n; i++) {
        if(ok[i]) {
            arr[++m] = {a[i], s[i]};
        }
    }
    arr[0].first = d;
    sort(arr + 1, arr + 1 + m, cmp);
    for(int i = 1; i <= m; i++) {
        pos[arr[i].second].push_back(i);
        upd(0, m, i, m, -1, 1);
    }
    int cur = 1, mxa = 0;
    for(int i = 1; i <= m; i++) {
        while(cur < arr[i].first) {
            for(int p : pos[cur]) {
                upd(0, m, p, m, 1, 1);
            }
            cur++;
        }
        int tarp = upper_bound(arr + 1, arr + 1 + i, PII{arr[i].second, INF}, cmp) - arr - 1;
        dp[i] = que(0, m, 0, tarp, 1) - que(0, m, i - 1, i - 1, 1) + dp[i - 1] + 1;
        upd(0, m, i, i, dp[i], 1);
        mxa = max(mxa, dp[i]);
    }
    ans += mxa;
    cout << ans << endl;
}
posted @ 2021-10-27 23:36  limil  阅读(62)  评论(0编辑  收藏  举报