【LGR-081】洛谷 1 月月赛 Div.2

写在前面

咕咕咕~

A

Link

\(T\) 组数据,每次给定一长度为 \(n\) 的 01 串,现可以选择任意个 0 修改为 1。
\(x\) 为数组中最长连续 1 子段的长度,\(y\) 为修改的元素的个数。要求最大化 \(x-y\),并构造一个方案。
\(1\le T\le 10\)\(1\le n\le 10^5\)
1S,128MB。

每进行一次操作,都至少会使最长连续 1 子段的长度 \(+1\)\(x-y\) 只增不降。
则一种最优方案是使原串变为全 1 串,\(x-y\) 的值是原串中 0 的个数。
复杂度 \(O(Tn)\)

B

Link

给定一长度为 \(n\) 的数列 \(k\),定义:

\[f(x,y) = \begin{cases} \min(k_x, k_y)\times (x + y) &(x\not= y)\\ k_x \times x &(x=y) \end{cases}\]

求:

\[\max_{x=1}^{n}\max_{y=1}^{n} f(x,y) \]

\(1\le n\le 10^6\)\(1\le k_i\le 10^9\)
1S,128MB。

\(x=y\) 的情况可以在读入时顺便处理,仅考虑 \(x\not= y\) 的情况。
一种显然的想法是枚举 \(x\),找到 \(\min(k_x,k_y) = x\) 的最优的 \(y\)\(y\) 显然越大越好,则最优的 \(k_y\) 是从数列右侧数第一个不小于 \(k_x\) 的元素。
排序后遍历数列即可解决,复杂度 \(O(n\log n)\)


出题人给出了强制线性做法的加强版:Link。菜鸡 Luckyblock 找不到能够维护 数列右侧第一个不小于 x 的元素 的小常数线性做法,上述思路在加强版中暂时认为无法通过。
考虑统计答案时钦定 \(x<y\),正序枚举 \(y\),考虑 \(1\sim y-1\) 中哪些元素作为 \(x\) 时贡献最大。显然,若存在 \((i < j <y)\land \,(k_i <k_j)\),则一定有 \(f(k_j,y)>f(k_i,y)\)。发现有贡献的 \(x\) 存在单调性。

考虑在枚举 \(y\) 的同时,对 \(1\sim y - 1\) 中的元素维护一个单调栈,单调栈中自顶向下元素下标递减,\(k\) 递增
枚举到一个新 \(y\) 时,考察栈顶元素 \(k_x\)\(k_y\) 的大小关系。若 \(k_x \le k_y\),发现 \(k_y\) 对之后的数配对的贡献大于与 \(k_x\) 配对的,则统计 \(f(x,y)\) 的贡献,并令 \(x\) 出栈。 否则对于所有栈中元素都有 \(\min(k_x,k_y) = k_y\),最右侧的 \(x\)\(y\) 配对贡献最大,统计它的贡献,并令 \(y\) 入栈。每个元素只会入栈出栈各一次,总复杂度 \(O(n)\) 级别。

下面这份代码交题时编译选项请选择 C++,选 C++11 不知道为什么过不去。

/ruo

//知识点:单调栈
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#define LL long long
const int kMaxn = 1e7 + 10;
//=============================================================
int n, top, a[kMaxn], st[kMaxn];
LL ans;
//=============================================================
#define getchar()                                                          \
  (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) \
       ? EOF                                                               \
       : *p1++)
char buf[1 << 21], *p1 = buf, *p2 = buf;
inline int read() {
  char c = getchar();
  int x = 0;
  bool f = 0;
  for (; !isdigit(c); c = getchar()) f ^= !(c ^ 45);
  for (; isdigit(c); c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
  if (f) x = -x;
  return x;
}
void Chkmax(LL &fir_, LL sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
//=============================================================
int main() {
  n = read();
  for (int i = 1; i <= n; ++ i) {
    a[i] = read();
    Chkmax(ans, 1ll * i * a[i]);
    while (top && a[st[top]] <= a[i]) {
      Chkmax(ans, 1ll * a[st[top]] * (st[top] + i));
      -- top;
    }
    if (top) Chkmax(ans, 1ll * a[i] * (st[top] + i));
    st[++ top] = i;
  }
  printf("%lld\n", ans);
  return 0;
}

C

Link

给定一长度为 \(n\) 的数列 \(A\),给定参数 \(s\),给定两种操作:

  1. 花费 \(a\) 的代价,将数列 \(A\) 中任意一个子串中的元素全部加 1。
  2. 花费 \(b\) 的代价,将数列 \(A\) 中任意一个子串中的元素全部乘 2。

两种操作进行的顺序任意,可以进行任意多次,求至少花费多少代价能使得数列 \(A\) 中存在一个子区间的元素之和不小于 \(s\)
\(1\le n\le 10^5\)\(1\le |A_i|,s,a,b\le 10^9\)
1S,128MB。

首先有两个显然的结论,所有操作 1 一定是全局使用的。操作 1 一定在 操作 2 之前。正确性显然,可以通过反证法得到不满足结论一定不会更优。
记操作 \(1\)\(2\) 进行的次数分别为 \(\operatorname{cnt}_a, \operatorname{cnt}_b\)。由于\(s\le 10^9\),则 \(\operatorname{cnt}_b\) 一定不大于 \(\log 10^9\) 次。
考虑枚举操作 \(b\) 进行的次数,问题转化为找到一个最小的非负整数 \(\operatorname{cnt}_a\),满足:

\[\exist 1\le l\le r\le n,\ 2^{\operatorname{cnt}_b}\times\sum_{i=l}^{r} (A_i + \operatorname{cnt}_a)\ge s \]

\(\operatorname{cnt}_a\) 越大,\(\sum_{i=l}^{r} (A_i + \operatorname{cnt}_a)\) 越大,上式左侧满足单调性。考虑二分 \(\operatorname{cnt}_a\),找到最小的满足上式的值即为最优的 \(\operatorname{cnt}_a\)Check\(O(n)\) 地求得新数列的最大子段和,检查是否满足上式即可。
总复杂度 \(O(n\log^2 w)\) 级别。


感谢涛哥!

赛时 Lb 的另一种理解方式。考虑把上面的式子再化一下:

\[\begin{aligned} \exist 1\le l\le r\le n,\ 2^{\operatorname{cnt}_b}\times\sum_{i=l}^{r} (A_i + \operatorname{cnt}_a)&\ge s\\ (r - l + 1)\operatorname{cnt}_a + \sum_{i=l}^{r} A_i &\ge \dfrac{s}{2^{\operatorname{cnt}_b}}\\ \operatorname{cnt}_a&\ge \dfrac{\dfrac{s}{2^{\operatorname{cnt}_b}} - \sum\limits_{i=l}^{r} A_i}{r - l + 1} \end{aligned}\]

显然 \(\operatorname{cnt}_a = \frac{\frac{s}{2^{\operatorname{cnt}_b}} - \sum_{i=l}^{r} A_i}{r - l + 1}\) 时最优。发现这个式子是一个并不显然的 01 分数规划的形式。它可以看做有 \(n\) 个物品 \((A_i,1)\),钦定必须选连续的一段物品,最小化:

\[\dfrac{\dfrac{s}{2^{\operatorname{cnt}_b}} - \sum\limits_{i=1}^{n} w_i \cdot A_i}{\sum\limits_{i=1}^{n}w_i\cdot 1} \]

考虑二分答案最小化 \(\operatorname{cnt}_a\)

\[\begin{aligned} \dfrac{\dfrac{s}{2^{\operatorname{cnt}_b}} - \sum\limits_{i=l}^{r} A_i}{r - l + 1} &\le mid\\ \dfrac{s}{2^{\operatorname{cnt}_b}} - \sum\limits_{i=l}^{r} A_i &\le (r - l + 1)mid\\ \sum\limits_{i=l}^{r} (A_i + mid)&\ge \dfrac{s}{2^{\operatorname{cnt}_b}} \end{aligned}\]

得到了与上面本质相同的式子。

注意某些地方可能会炸 LL。

//知识点:二分答案,DP
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kMaxn = 1e5 + 10;
const LL kInf = 1e18 + 2077;
//=============================================================
LL n, a, b, s, ans = kInf, val[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(LL &fir_, LL sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(LL &fir_, LL sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
bool Check(LL mid_, LL x_) {
  LL sum = 0;
  for (int i = 1; i <= n; ++ i) {
    if (sum > 0) {
      sum += val[i] + mid_;
    } else {
      sum = val[i] + mid_;
    }
    if (1.0 * sum >= 1.0 * s / x_) return true; //如果写成 (x_ * sum > s) 会炸 LL
  }
  return false;
}
//=============================================================
int main() {
  n = read(), a = read(), b = read(), s = read();
  for (int i = 1; i <= n; ++ i) val[i] = read();
  for (LL i = 0, x = 1; i <= 32; ++ i, x <<= 1ll) {
    LL numa = kInf;
    for (LL l = 0, r = kInf; l <= r; ) {
      LL mid = (l + r) >> 1ll;
      if (Check(mid, x)) {
        numa = mid;
        r = mid - 1;
      } else {
        l = mid + 1;
      }
    }
    Chkmin(ans, numa * a + b * i);
  }
  printf("%lld\n", ans);
  return 0;
}

D

Link

给定一为 \(n\) 个节点的树,第 \(i\) 个点的点权为 \(a_i\)
对于三个连通块 \(A,B,C\),定义 \(f(A,B,C)\) 为:

\[f(A,B,C)=\left(\sum_{u\in A} a_u\right)\times \left(\sum_{u\in B} a_u\right)\times \left(\sum_{u\in C} a_u\right) \]

给定 \(m\) 次询问,每次选定一条边删去,求再删去一条边得到的所有情况中,分裂出的三个连通块 \(A,B,C\)\(f(A,B,C)\) 的和。
\(2\le n,m\le 10^6\)\(0\le a_i\le 10^6\)
1.5S,512MB。

赛时一眼是个没啥意思的换根,就懒得写了= =

posted @ 2021-01-25 10:24  Luckyblock  阅读(88)  评论(0编辑  收藏  举报