ARC169 补

A Please Sign

给定一棵大小为 \(n\) 的树,每个点 \(i\) 有一个点权 \(a_i\),每次操作每个父亲的点权加上儿子,问进行足够多次操作后根节点 \(1\) 的点权正负性。

Solution

儿子对答案的贡献比父亲大,因为父亲的点权会受到儿子的点权干扰。深度越深,对答案的贡献越大。

因此只需要对每个深度的点的点权和,由深到浅枚举,如果不为 \(0\) 即得答案。

\(\\\)

Code
// STOOOOOOOOOOOOOOOOOOOOOOOOO hzt CCCCCCCCCCCCCCCCCCCCCCCORZ
#include <algorithm>
#include <iostream>
#include <numeric>
#include <vector>

using namespace std;
using LL = long long;
using PII = pair<int, int>;
constexpr int kN = 2.5e5 + 1;

int n, a[kN], d[kN];
vector<int> e[kN];
LL s[kN];
int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n;
  for (int i = 1; i <= n; i++)
    cin >> a[i];
  s[0] = a[1];
  for (int i = 2, x; i <= n; i++) {
    cin >> x;
    d[i] = d[x] + 1;
    s[d[i]] += a[i];
  }
  for (int i = n; i >= 0; i--) {
    if (s[i] != 0) {
      cout << (s[i] > 0 ? '+' : '-') << '\n';
      return 0;
    }
  }
  cout << "0\n";
  return 0;
}

B Subsegments with Small Sums

给定一个长度为 \(n\) 的序列 \(a\),一个序列的权值是,最少能分割成多少段,使得每一段的和不超过 \(s\)。问 \(a\) 的所有区间的权值和。

Solution

考虑对每个左端点 \(l\) 计算贡献。显然只要以 \(l\) 为左端点的区间,选用以 \(l\) 为左端点的最长段为第一段总不劣。这段的右端点 \(r\) 可以二分找出。因此相较于 \(f_{r+1}\)\(f_l\) 对答案的贡献是对于每个以 \(l\) 为左端点的区间答案 \(+1\)。因此 \(f_l=f_{r+1}+n-l+1\)

\(\\\)

Code
// STOOOOOOOOOOOOOOOOOOOOOOOOO hzt CCCCCCCCCCCCCCCCCCCCCCCORZ
#include <algorithm>
#include <iostream>
#include <numeric>
#include <vector>

using namespace std;
using LL = long long;
using PII = pair<int, int>;
constexpr int kN = 2.5e5 + 2;

LL n, x, a[kN];
LL s[kN], f[kN];
int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> x;
  for (int i = 1; i <= n; i++) {
    cin >> a[i];
    s[i] = s[i - 1] + a[i];
  }
  f[n] = 1;
  for (int i = n - 1; i >= 1; i--) {
    int p = upper_bound(s + 1, s + n + 1, s[i] + x - a[i]) - s;
    f[i] = f[p] + n - i + 1;
  }
  cout << accumulate(f + 1, f + n + 1, 0ll) << '\n';
  return 0;
}

C Not So Consecutive

给定一个由 \(-1\)\([1,n]\) 组成的长度为 \(n\) 的序列 \(a\),在 \(-1\) 上填数使不存在连续出现 \(i\)\(a_i\),求方案数。

我无法比写得比他更好

D Add to Make a Permutation

给定一个长度为 \(n\) 的序列 \(a\),每次操作选择 \(m\) 个位置将其加一,并对 \(n\) 取模。问最少多少次操作可以将其变为一个 \(n\) 排列。

Solution

\(b\) 表示不取模的答案序列。易得以下三点限制:

  • \(a_i\leq b_i\) (操作只加不减)
  • \(\sum(b_i-a_i)|m\) (单点加总次数必须被 \(m\) 整除)
  • \(b_i-a_i\leq \frac{\sum(b_i-a_i)}{m}\) (单点加次数不超过操作次数)

只需要满足这三点,就可以保证 \(b\) 是一个合法的操作序列。显然可以将 \(a\)\(b\) 分别排序对应,这样只会更可能满足第三点。

注意到 \(b\) 一定可以是形如 \((x,x+1,\cdots,x+n-1)\) 的一个序列。

Proof

运用调整法,如果 \(b_n-b_1\geq n\),每次操作将 \(b_1\) 改为之前的 \(b_n-n\),将 \(b_n\) 改为之前的 \(b_1+n\)

首先,这样调整并不会改变整体和,操作次数是不变的。然后对比每个合法条件:

  • 因为 \(b_n-b_1\geq n\),有 \(a_1\leq b_1\leq b_n-n\)。因为 \(a_n-a_1<n\),所以 \(b_1\geq a_1>a_n-n, b_1+n>a_n\)
  • 没有改变 \(\sum b\),也不会改变 \(\sum b_i-a_i\)
  • \(b_n\) 变小,\(b_n-b_1\) 减小,保持满足。同时因为 \(a_n<a_1+n\),有 \(b_n-a_n>b_1+n-(a_1+n)=b_1-a_1\)\(n\) 能满足 \(1\) 也能满足。

一次调整后,首位极端值都落到了中间,持续排序、调整,总能让首尾差距不到 \(n\)

接下来就是找合法的 \(x\) 了。需要满足前两个条件,可以根据第一个条件给出的下界枚举,由于 \(x\)\(n\) 对答案无影响,可以直接枚举找到满足第二个条件的。找不到显然无解。

第三个条件再次限定了 \(x\) 的最小值。如果现在 \(b_i-a_i\leq\frac{\sum b_i-a_i}{m}\),那么增加一个 \(\Delta x\),有 \(b_i-a_i+\Delta x\leq\frac{n\Delta x+\sum b_i-a_i}{m}\)。显然为了不等式右侧操作次数为整数,有 \(m| n\Delta x\)。每次 \(\Delta x\) 至少调整 \(\frac{m}{\gcd(n,m)}\),每次调整不等式左侧(最大差)增加 \(\frac{m}{\gcd(n,m)}\),右侧(操作次数)增加 \(\frac{n}{\gcd(n,m)}\)。一直推直到满足条件即可。显然调整后仍然满足第二个条件。

由于 \(m<n\),操作次数显然大于 \(b_i-a_i\) 的平均数,又因为序列 \(a\)\(b\) 的极差都小于 \(n\)\(\frac{n}{\gcd(n,m)}>\frac{m}{\gcd(n,m)}\),每次只要能拉回 \(1\) 的差距就是 \(O(n)\) 的,因此此处非复杂度瓶颈。

复杂度 \(O(n\log n)\),瓶颈在排序。

\(\\\)

Code
// STOOOOOOOOOOOOOOOOOOOOOOOOO hzt CCCCCCCCCCCCCCCCCCCCCCCORZ
#include <algorithm>
#include <iostream>
#include <numeric>
#include <vector>

using namespace std;
using LL = long long;
using PII = pair<int, int>;
constexpr int kN = 2.5e5 + 1;

LL n, m, a[kN];
int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> m;
  for (int i = 0; i < n; i++) {
    cin >> a[i];
  }
  sort(a, a + n);
  LL mnx = 0, s = 0;
  for (int i = 0; i < n; i++) {
    mnx = max(mnx, a[i] - i);
    s += i - a[i];
  }
  for (int x = mnx; x < mnx + m; x++) {
    if ((s + 1ll * n * x) % m != 0)
      continue;
    LL mx = 0;
    for (int i = 0; i < n; i++) {
      mx = max(mx, x + i - a[i]);
    }
    LL op = (s + 1ll * x * n) / m, g = __gcd(n, m);
    for (; op < mx;) {
      op += n / g;
      mx += m / g;
    }
    cout << op << '\n';
    return 0;
  }
  cout << "-1\n";
  return 0;
}

E

posted @ 2024-08-01 14:44  Lightwhite  阅读(30)  评论(1)    收藏  举报