DMOJ

B. Infinity Card Decks

题目描述

\(N\) 张牌,第 \(i\) 张牌打出需要 \(A_i\) 能量,获得 \(B_i\) 能量。一开始你有 \(M\) 的能量。

如果一些牌,无论怎么无限的按照随机顺序打出,都不会缺少能量,则我们称这是一个无限牌组

求有多少个子区间是无限牌组。

思路

很容易想到,一个无限牌组必须满足以下条件。

  1. \(\sum A_i \le \sum B_i\),因为如果不满足该条件,那么每经过一轮能量就会减少,所以最终一定会不够。
  2. 在第一轮打出时不可能缺少能量。因为满足条件 1,所以能量每过一轮都是单调不降的,所以第一轮的能量是最少的。

我们先来看条件 2:

  • 我们要枚举一张牌 \(i\),使得在出这张牌的时候能量不足。在这之前,肯定会把其他 \(A_j>B_j且i\ne j\)\(j\) 出掉。
    • 如果 \(A_i\le B_i\),那么此时要满足 \(M\ge A_i+\sum \max(0,A_j-B_j)\)
    • 否则如果 \(A_i>B_i\),那么此时要满足 \(M\ge A_i+(\sum \max(0,A_j-B_j)-(A_i-B_j))=B_i+\sum \max(0,A_j-B_j)\)
  • 上面两式合起来就是 \(M\ge \min (A_i,B_i)+\sum \max(0,A_j-B_j)\)

所以一个区间 \([l,r]\) 要满足 \(M\ge \max\limits_{i=l}^r\{\min (A_i,B_i)\}+\sum \limits_{i=l}^r \max(0,A_i-B_i)\)。这个使用双指针求解。

接着我们考虑满足条件 1,我们可以做一个 \(A_i-B_i\) 的前缀和,离散化后用树状数组统计数量即可。

空间复杂度 \(O(N)\),时间复杂度 \(O(N\log N)\)

代码

#include<bits/stdc++.h>
using namespace std;
using ll = long long;

const int MAXN = 1000005;

int n, m, a[MAXN], b[MAXN], log_2[MAXN];
ll ans, st[21][MAXN], pre[MAXN], tr[MAXN];
vector<ll> X;

int Getmax(int l, int r) {
  return max(st[log_2[r - l + 1]][l], st[log_2[r - l + 1]][r - (1 << log_2[r - l + 1]) + 1]);
}

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

void update(int p, ll x) {
  for(; p <= n + 1; tr[p] += x, p += lowbit(p)) {
  }
}

ll Getsum(int p) {
  ll sum = 0;
  for(; p; sum += tr[p], p -= lowbit(p)) {
  }
  return sum;
}

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n >> m;
  for(int i = 1; i <= n; ++i) {
    cin >> a[i];
  }
  for(int i = 1; i <= n; ++i) {
    cin >> b[i];
    pre[i] = pre[i - 1] + a[i] - b[i];
    X.emplace_back(pre[i]);
    st[0][i] = min(a[i], b[i]);
  }
  X.emplace_back(0);
  sort(X.begin(), X.end()), X.erase(unique(X.begin(), X.end()), X.end());
  for(int i = 0; i <= n; ++i) {
    pre[i] = lower_bound(X.begin(), X.end(), pre[i]) - X.begin() + 1;
  }
  for(int i = 1; i <= 20; ++i) {
    for(int j = 1; j <= n; ++j) {
      if(j + (1 << i) - 1 <= n) {
        st[i][j] = max(st[i - 1][j], st[i - 1][j + (1 << (i - 1))]);
      }
    }
  }
  for(int i = 2; i <= n; ++i) {
    log_2[i] = log_2[i / 2] + 1;
  }
  ll sum = 0;
  for(int i = 1, j = 1; i <= n; sum -= max(0, a[i] - b[i]), update(pre[i], -1), ++i) {
    for(; j <= n && Getmax(i, j) + sum + max(0, a[j] - b[j]) <= m; sum += max(0, a[j] - b[j]), update(pre[j], 1), ++j) {
    }
    ans += Getsum(pre[i - 1]);
    if(j == i) {
      sum += max(0, a[j] - b[j]), update(pre[j], 1), ++j;
    }
  }
  cout << ans;
  return 0;
}

C. Permutation Sorting

题目描述

有一个长度为 \(N\) 的排列 \(A\),有 \(M\) 次操作。每次操作为将一段区间升序/降序排序,求最后位置 \(p\) 上的数字。

思路

考虑二分答案。

我们假设二分出 \(x\),由于我们只关心 \(A_p\) 是否 \(\le x\),所以我们令每个 \(A_i\) 变作 \(\begin{cases}0&A_i\le x\\1&A_i>x\end{cases}\),这样我们就能通过线段树求出区间内 \(0,1\) 的数量来排序了。

空间复杂度 \(O(N)\),时间复杂度 \(O((N+M\log N)\log N)\)

代码

#include<bits/stdc++.h>
using namespace std;

const int MAXN = 100001;

struct Segment_Tree {
  int l[MAXN << 2], r[MAXN << 2], a[MAXN], cnt[MAXN << 2][2], lazy[MAXN << 2];
  void build(int u, int s, int t) {
    l[u] = s, r[u] = t, lazy[u] = -1;
    if(s == t) {
      cnt[u][0] = (a[s] == 0), cnt[u][1] = (a[s] == 1);
      return;
    }
    int mid = (s + t) >> 1;
    build(u << 1, s, mid), build((u << 1) | 1, mid + 1, t);
    cnt[u][0] = cnt[u << 1][0] + cnt[(u << 1) | 1][0];
    cnt[u][1] = cnt[u << 1][1] + cnt[(u << 1) | 1][1];
  }
  void tag(int u, int x) {
    cnt[u][x] = r[u] - l[u] + 1, cnt[u][!x] = 0, lazy[u] = x;
  }
  void pushdown(int u) {
    if(lazy[u] != -1) {
      tag(u << 1, lazy[u]), tag((u << 1) | 1, lazy[u]), lazy[u] = -1;
    }
  }
  void update(int u, int s, int t, int x) {
    if(s > t) {
      return;
    }
    if(l[u] >= s && r[u] <= t) {
      tag(u, x);
      return;
    }
    pushdown(u);
    if(s <= r[u << 1]) {
      update(u << 1, s, t, x);
    }
    if(t >= l[(u << 1) | 1]) {
      update((u << 1) | 1, s, t, x);
    }
    cnt[u][0] = cnt[u << 1][0] + cnt[(u << 1) | 1][0];
    cnt[u][1] = cnt[u << 1][1] + cnt[(u << 1) | 1][1];
  }
  int Getsum(int u, int s, int t, int x) {
    if(l[u] >= s && r[u] <= t) {
      return cnt[u][x];
    }
    pushdown(u);
    int v = 0;
    if(s <= r[u << 1]) {
      v += Getsum(u << 1, s, t, x);
    }
    if(t >= l[(u << 1) | 1]) {
      v += Getsum((u << 1) | 1, s, t, x);
    }
    return v;
  }
}tr;

struct query {
  int op, l, r;
}s[MAXN];

int n, m, a[MAXN], p;

bool check(int x) {
  for(int i = 1; i <= n; ++i) {
    tr.a[i] = (a[i] > x);
  }
  tr.build(1, 1, n);
  for(int i = 1; i <= m; ++i) {
    int _0 = tr.Getsum(1, s[i].l, s[i].r, 0);
    if(!s[i].op) {
      tr.update(1, s[i].l, s[i].l + _0 - 1, 0);
      tr.update(1, s[i].l + _0, s[i].r, 1);
    }else {
      tr.update(1, s[i].r - _0 + 1, s[i].r, 0);
      tr.update(1, s[i].l, s[i].r - _0, 1);
    }
  }
  return tr.Getsum(1, p, p, 1);
}

int Binary_Search() {
  int l = 1, r = n + 1;
  for(; l < r; ) {
    int mid = (l + r) >> 1;
    (!check(mid) ? r = mid : l = mid + 1);
  }
  return l;
}

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n >> m;
  for(int i = 1; i <= n; ++i) {
    cin >> a[i];
  }
  for(int i = 1; i <= m; ++i) {
    cin >> s[i].op >> s[i].l >> s[i].r;
  }
  cin >> p;
  cout << Binary_Search();
  return 0;
}

D. Yet Another Contest 8 P3 - Herobrine

题目描述

你在玩 \(\texttt{Minecraft}\),有 \(N+1\) 个矿井,其中 \(0\) 是外部。每个矿井 \(i(1\le i\le N)\) 都有一条连向 \(P_i(0\le P_i<i)\) 的道路。在矿井 \(i(1\le i\le N)\)\(M_i\) 个矿物 \(O_{i,1},O_{i,2},\dots,O_{i,M_i}\)。你一开始可以选择一个矿物集合 \(X\),接下来你可以进行以下操作:

  • 使用 \(X\) 中的每个矿石一个,合成出一个力量为 \(|X|\)\(\texttt{Herobrine}\)
  • 使用两个力量分别为 \(A,B\)\(\texttt{Herobrine}\) 合成出一个力量为 \(A+B\)\(\texttt{Herobrine}\)

这时,连接 \(C\)\(P_C\) 的通道被基岩封死了,所以你想知道用剩余可挖掘的矿物能合成出的 \(\texttt{Herobrine}\) 的最大力量。

对每个 \(1\le C\le N\) 求解该答案。

思路

我们记 \(cnt_i\) 表示数量 \(\ge i\) 的不同矿物数量,如果我们加入了一个矿石 \(x\),其原本出现了 \(y\) 次,那么 \(y\leftarrow y+1,cnt_y\leftarrow cnt_y+1\)。很显然答案为 \(\max\{cnt_i\cdot i\}\)

但是如果我们用普通的启发式合并是双 \(\log\) 的。所以我们要用一种特殊的启发式合并。我们用一个全局数组 \(cnt\)。令一个点的儿子中矿物最多的为重儿子,其余为轻儿子。我们对于一个点先递归其轻儿子,并清空 \(cnt\),接着递归其重儿子,但不清空,最后把该节点和其他轻儿子也放进来,并求解答案即可。

空间复杂度 \(O(N)\),时间复杂度 \(O(N\log N)\)

代码

#include<bits/stdc++.h>
using namespace std;
using ll = long long;

const int MAXN = 1000005, MAXV = 2000005;

int n, dfn[MAXN], sz[MAXN], son[MAXN], cnt[MAXV], minecnt[MAXN];
ll ans[MAXN], res;
vector<int> e[MAXN], ve[MAXN], mine;

void dfs(int u) {
  dfn[u] = mine.size(), sz[u] = ve[u].size();
  for(int x : ve[u]) {
    mine.emplace_back(x);
  }
  son[u] = -1;
  for(int v : e[u]) {
    dfs(v);
    sz[u] += sz[v];
    if(son[u] == -1 || sz[v] > sz[son[u]]) {
      son[u] = v;
    }
  }
}

void DFS(int u) {
  for(int v : e[u]) {
    if(v != son[u]) {
      DFS(v);
      for(int i = dfn[v]; i <= dfn[v] + sz[v] - 1; ++i) {
        cnt[minecnt[mine[i]]--]--;
      }
      res = 0;
    }
  }
  if(son[u] != -1) {
    DFS(son[u]);
  }
  if(!u) {
    return;
  }
  for(int x : ve[u]) {
    cnt[++minecnt[x]]++;
    res = max(res, 1ll * minecnt[x] * cnt[minecnt[x]]);
  }
  for(int v : e[u]) {
    if(v != son[u]) {
      for(int i = dfn[v]; i <= dfn[v] + sz[v] - 1; ++i) {
        cnt[++minecnt[mine[i]]]++;
        res = max(res, 1ll * minecnt[mine[i]] * cnt[minecnt[mine[i]]]);
      }
    }
  }
  ans[u] = res;
}

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n;
  for(int i = 1, x; i <= n; ++i) {
    cin >> x;
    e[x].emplace_back(i);
  }
  for(int i = 1, k; i <= n; ++i) {
    cin >> k;
    for(int j = 1, x; j <= k; ++j) {
      cin >> x;
      ve[i].emplace_back(x);
    }
  }
  dfs(0);
  DFS(0);
  for(int i = 1; i <= n; ++i) {
    cout << ans[i] << "\n";
  }
  return 0;
}
posted @ 2024-09-29 23:28  Yaosicheng124  阅读(4)  评论(0编辑  收藏  举报