【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
不知道为什么过不去。
//知识点:单调栈
/*
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\),给定两种操作:
- 花费 \(a\) 的代价,将数列 \(A\) 中任意一个子串中的元素全部加 1。
- 花费 \(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\),满足:
\(\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
的另一种理解方式。考虑把上面的式子再化一下:
显然 \(\operatorname{cnt}_a = \frac{\frac{s}{2^{\operatorname{cnt}_b}} - \sum_{i=l}^{r} A_i}{r - l + 1}\) 时最优。发现这个式子是一个并不显然的 01 分数规划的形式。它可以看做有 \(n\) 个物品 \((A_i,1)\),钦定必须选连续的一段物品,最小化:
考虑二分答案最小化 \(\operatorname{cnt}_a\)。
得到了与上面本质相同的式子。
注意某些地方可能会炸 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。
赛时一眼是个没啥意思的换根,就懒得写了= =