Codeforces Round 924 (Div. 2)
1928D - Lonely Mountain Dungeons
题意:
有 \(n\) 个种族,第 \(i\) 个种族 \(c_i\) 个生物,现在要将这些生物分成若干组,每一对不在同一组但是同一种族的生物会对这种分组的价值贡献 \(b\),如果分了 \(k\) 组,则价值要减去 \((k-1)x\),求最大价值。
\(n \le 10^5,\sum c_i \le 10^5\)
思路:
对每种生物单独考虑。
假设已知分了 \(k\) 组,显然对于 \(c_i\),分成若干个 \(\frac{c_i}{k}\) 最优。
具体的,设 \(d = \lfloor\frac{c_i}{k}\rfloor,r = c_i \bmod k\),则我们分 \(r\) 组 \(d + 1\) 个,分 \(k-r\) 组 \(d\) 个,总共有 \(\binom{c_i}{2}-r\binom{d+1}{2}-(k-r)\binom{d}{2}\)。
总贡献应该是个是个单峰函数。
于是我们可以三分,当然二分会更好些一点,每次判断 \(f(mid)\) 与 \(f(mid+1)\) 的关系判断最大值在哪个区间即可。
时间复杂度 \(O(n \log \max c_i)\)。
考虑到生物总数比较少,我们也可以直接对每个生物枚举 \(k = 1 \sim c_i\),这样做就不用管他单不单峰了。
点击查看代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e5 + 5;
int n, b, x;
int c[N] = {0};
long long cmb(int m) {
return 1ll * m * (m - 1) / 2;
}
long long chk(int k) {
long long ans = 0ll;
for (int i = 1; i <= n; i++) {
long long d = c[i] / k;
long long m = c[i] % k;
ans += cmb(c[i]);
if (c[i] >= k)
ans -= m * cmb(d + 1) + (k - m) * cmb(d);
}
return ans * b - 1ll * (k - 1) * x;
}
void slv() {
cin >> n >> b >> x;
int mx = 0;
for (int i = 1; i <= n; i++)
cin >> c[i], mx = max(mx, c[i]);
int l = 1, r = mx + 1;
while (l + 1 < r) {
int mid = (l + r) / 2;
if (chk(mid - 1) <= chk(mid))
l = mid;
else
r = mid;
}
//cout << l << endl;
cout << chk(l) << endl;
}
int main() {
int T;
cin >> T;
while (T--)
slv();
return 0;
}
1928E - Modular Sequence
题意:
\(n\) 长度序列 \(a\) 满足: \(a_i = a_{i - 1} \bmod y\) 或 \(a_i = a_{i-1} + y\),\(1 < i \le n\)。现在给定 \(x, y, S\),求出一个序列 \(a\) 满足上述性质,第一个数为 \(x\) 且所有数之和为 \(S\)。
\(n \le 2 \times 10^5, S \le 2 \times 10^5\)
思路:
显然最终答案是 \(x, x + 1, \dots, x + k_1\) 和若干个 \(x \bmod y, x \bmod y + 1, \dots, x \bmod y + k_i\) 的形式。
先将所有 \(x \bmod y\) 减去,再将所有数除以 \(y\),记 \(b = \frac{x - x \bmod y}{y}\)。
现在答案就是 \(b * k_1\) 加上若干个 \(0, 1, 2, \dots, k_i\)。
可以枚举 \(k_1\),现在判断能否有若干个等差序列和为 \(sum\)。
值域不大,考虑 \(dp\)。设 \(dp[i]\) 表示和为 \(i\),所有等差序列至少几个元素。假设最少 \(k\) 个,则比 \(k\) 大的都可以通过补 \(0\) 实现。
直接正着转移,等差序列的和是平方级别的,所有总时间复杂度是 \(O(s\sqrt s)\)。
于是就完事儿了,输出方案倒着推一遍即可。
点击查看代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e5 + 5;
const int inf = 0x3f3f3f3f;
int n, x, y, s;
int dp[N] = {0};//dp[i] 表示最少几个元素的若干个等差序列和为 i
void DP() {
for (int i = 0; i <= s; i++)
dp[i] = inf;
dp[0] = 0;
for (int i = 0; i <= s; i++)
for (int j = i + 1, k = 1; j <= s; k++, j += k)
dp[j] = min(dp[j], dp[i] + k + 1);
}
void slv() {
cin >> n >> x >> y >> s;
DP();
int r = x % y;
for (int i = 1; i <= n; i++) {
//从 x, x + 1, x + 2, ..., x + (i - 1)
int res = s;
if (1ll * (i - 1) * i / 2 * y + 1ll * x * i + 1ll * (n - i) * r > res)
continue;
res -= 1ll * (i - 1) * i / 2 * y + 1ll * x * i + 1ll * (n - i) * r;
if (res % y != 0)
continue;
res /= y;
if (dp[res] <= n - i) {
cout << "YES" << endl;
for (int j = 1; j <= i; j++)
cout << x + (j - 1) * y << " ";
int cur = res;
while (cur > 0) {
for (int p = cur - 1, j = 1; p >= 0; j++, p -= j)
if (dp[cur] == dp[p] + j + 1) {
for (int k = 0; k <= j; k++)
cout << r + k * y << " ";
cur = p;
break;
}
}
for (int j = 1; j <= n - i - dp[res]; j++)
cout << r << " ";
cout << endl;
return;
}
}
cout << "NO" << endl;
}
int main() {
int T;
cin >> T;
while (T--)
slv();
return 0;
}
1928F - Digital Patterns
题意:
给定两个序列 \(a_1, a_2, \dots, a_n\) 和 \(b_1, b_2, \dots, b_m\),两个序列构成一个 \(n \times m\) 表格,\((i,j)\) 的价值定义为 \(a_i + b_j\),若干次对 \(a\) 或 \(b\) 区间加,要求每次修改完后输出表格中有多少个子正方形,满足内部四相邻的点价值不同。
\(n,m,q \le 10^5\)。
思路:
很有启发性的一道题。
首先观察发现,所有 \(a_i = a_{i+1}\) 和 \(b_i = b_{i+1}\) 的位置将表格分成若干个块,内部的所有子正方形都满足条件,任何在两个块的子正方形都不满足条件。
我们假设对于一个块长宽为 \(n,m(n \le m)\),则贡献就是:
这个推导过程可以直接全拆开再合并,反正最终能得到这个结果。
我们考虑四个数列 \(a_n = 1, b_n = n, c_n = \frac{n(n+1)}{2}, d_n = \frac{n(n+1)(2n+1)}{6}-\frac{n^2(n+1)}{2}\)。
这样构造数列会使得 \(f(n,m) = a_m d_n + b_m c_n\)。注意这是 \(n \le m\) 的情况,否则 \(n,m\) 要对调一下。
现在考虑两个序列分成了若干块,分别长度为 \(n_1,n_2,\dots,n_k\) 和 \(m_1,m_2,\dots,m_l\),则答案为:
对于固定的 \(x\),我们考虑怎么求 \(\sum_{i=1}^k f(x,m_i)\),按照上面的拆开就是:
所以我们可以维护 \(a, b, c, d\) 的前缀和以及 \(n_1,n_2,\dots, n_k\) 和 \(m_1,m_2,\dots,m_l\),将两个序列按从小到大来存,可以用 \(set\)。
一个数 \(n_i\) 的贡献就是上面的式子,我们通过快速计算贡献,具体可以用线段树来存前缀和值,一次修改改变常数个,所以最终时间复杂度是 \(O(n \log n)\) 的。
需要卡一下常。
点击查看代码
#include <iostream>
#include <cstdio>
#include <set>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 3e5 + 5;
#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;
void read(int &x) {
x = 0;
int f = 1;
char ch = getchar();
while (ch > '9' || ch < '0') {
if (ch == '-')
f = -1;
ch = getchar();
}
while (ch <= '9' && ch >= '0')
x = x * 10 + (ch - '0'), ch = getchar();
x = f * x;
}
void readLL(long long &x) {
x = 0;
int f = 1;
char ch = getchar();
while (ch > '9' || ch < '0') {
if (ch == '-')
f = -1;
ch = getchar();
}
while (ch <= '9' && ch >= '0')
x = x * 10 + (ch - '0'), ch = getchar();
x = f * x;
}
void write(long long x) {
if (x < 0)
putchar('-'), write(-x);
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
int n, m, q;
/*long long A(int x) {
return 1;
}*/
long long B(int x) {
return x;
}
long long C(int x) {
return 1ll * x * (x + 1) / 2;
}
long long D(int x) {
return 1ll * x * (x + 1) / 2 * (2ll * x + 1) / 3 - 1ll * x * (x + 1) / 2 * x;
}
struct SegTree {
long long val[N * 4];
#define ls (x << 1)
#define rs (x << 1 | 1)
#define mid ((lx + rx) >> 1)
void pushup(int x) {
val[x] = val[ls] + val[rs];
}
void build(int x, int lx, int rx) {
if (lx + 1 == rx) {
val[x] = 0ll;
return;
}
build(ls, lx, mid), build(rs, mid, rx);
pushup(x);
}
void mdf(int x, int lx, int rx, int pos, long long v) {
if (lx + 1 == rx) {
val[x] += v;
return;
}
(pos < mid) ? mdf(ls, lx, mid, pos, v) : mdf(rs, mid, rx, pos, v);
pushup(x);
}
long long qry(int x, int lx, int rx, int l, int r) {
if (rx <= l || r <= lx)
return 0ll;
if (l <= lx && rx <= r)
return val[x];
return qry(ls, lx, mid, l, r) + qry(rs, mid, rx, l, r);
}
SegTree () {}
SegTree (int _n) {
build(1, 1, _n + 1);
}
#undef ls
#undef rs
#undef mid
};
SegTree xsta, xstb, xstc, xstd;
SegTree ysta, ystb, ystc, ystd;
long long x[N] = {0}, y[N] = {0};
set<int> nx, ny;
long long tot = 0ll;//总贡献
//nx, ny 存 x[i] = x[i + 1] 或 y[i] = y[i + 1] 的 i (默认x[n] = x[n + 1] 和 y[m] = y[m + 1])
//转化成差分,存所有 d[i] = 0 的 i - 1
long long Fx(int t) {
long long sum = 0ll;
sum += 1ll * ystd.qry(1, 1, m + 1, 1, t + 1);
sum += B(t) * ystc.qry(1, 1, m + 1, 1, t + 1);
sum += C(t) * ystb.qry(1, 1, m + 1, t + 1, m + 1);
sum += D(t) * ysta.qry(1, 1, m + 1, t + 1, m + 1);
return sum;
}
long long Fy(int t) {
long long sum = 0ll;
sum += 1ll * xstd.qry(1, 1, n + 1, 1, t + 1);
sum += B(t) * xstc.qry(1, 1, n + 1, 1, t + 1);
sum += C(t) * xstb.qry(1, 1, n + 1, t + 1, n + 1);
sum += D(t) * xsta.qry(1, 1, n + 1, t + 1, n + 1);
return sum;
}
void setX(int t, int v) {
xsta.mdf(1, 1, n + 1, t, v);
xstb.mdf(1, 1, n + 1, t, v * B(t));
xstc.mdf(1, 1, n + 1, t, v * C(t));
xstd.mdf(1, 1, n + 1, t, v * D(t));
}
void setY(int t, int v) {
ysta.mdf(1, 1, m + 1, t, v);
ystb.mdf(1, 1, m + 1, t, v * B(t));
ystc.mdf(1, 1, m + 1, t, v * C(t));
ystd.mdf(1, 1, m + 1, t, v * D(t));
}
void addX(int pos) {//X 有一个位置满足
auto Pr = nx.lower_bound(pos);
Pr--;
auto Nx = nx.upper_bound(pos);
int A = pos - *Pr, B = *Nx - pos;
tot -= Fx(A + B);
tot += Fx(A) + Fx(B);
setX(A + B, -1);
setX(A, 1), setX(B, 1);
nx.insert(pos);
}
void eraseX(int pos) {//X 有一个位置不满足
auto Pr = nx.lower_bound(pos);
Pr--;
auto Nx = nx.upper_bound(pos);
int A = pos - *Pr, B = *Nx - pos;
tot += Fx(A + B);
tot -= Fx(A) + Fx(B);
setX(A + B, 1);
setX(A, -1), setX(B, -1);
nx.erase(pos);
}
void addY(int pos) {//y 有一个位置满足
auto Pr = ny.lower_bound(pos);
Pr--;
auto Nx = ny.upper_bound(pos);
int A = pos - *Pr, B = *Nx - pos;
tot -= Fy(A + B);
tot += Fy(A) + Fy(B);
setY(A + B, -1);
setY(A, 1), setY(B, 1);
ny.insert(pos);
}
void eraseY(int pos) {//y 有一个位置满足
auto Pr = ny.lower_bound(pos);
Pr--;
auto Nx = ny.upper_bound(pos);
int A = pos - *Pr, B = *Nx - pos;
tot += Fy(A + B);
tot -= Fy(A) + Fy(B);
setY(A + B, 1);
setY(A, -1), setY(B, -1);
ny.erase(pos);
}
int main() {
read(n), read(m), read(q);
for (int i = 1; i <= n; i++)
readLL(x[i]);
for (int j = 1; j <= m; j++)
readLL(y[j]);
//差分
for (int i = n; i >= 1; i--)
x[i] -= x[i - 1];
for (int j = m; j >= 1; j--)
y[j] -= y[j - 1];
//初始
nx.insert(n), ny.insert(m);
nx.insert(0), ny.insert(0);
xsta = SegTree(n);
xstb = SegTree(n);
xstc = SegTree(n);
xstd = SegTree(n);
ysta = SegTree(m);
ystb = SegTree(m);
ystc = SegTree(m);
ystd = SegTree(m);
setX(n, 1), setY(m, 1);
for (int k = 1; k <= min(n, m); k++)
tot += 1ll * k * k + 1ll * (max(n, m) - min(n, m)) * k;
for (int i = 2; i <= n; i++)
if (x[i] == 0)
addX(i - 1);
for (int j = 2; j <= m; j++)
if (y[j] == 0)
addY(j - 1);
write(tot);
putchar('\n');
for (int i = 1; i <= q; i++) {
int t, l, r, v;
read(t), read(l), read(r), read(v);
if (t == 1) {
if (x[l] == 0 && l > 1)
eraseX(l - 1);
if (x[r + 1] == 0 && r < n)
eraseX(r);
x[l] += v, x[r + 1] -= v;
if (x[l] == 0 && l > 1)
addX(l - 1);
if (x[r + 1] == 0 && r < n)
addX(r);
}
else {
if (y[l] == 0 && l > 1)
eraseY(l - 1);
if (y[r + 1] == 0 && r < m)
eraseY(r);
y[l] += v, y[r + 1] -= v;
if (y[l] == 0 && l > 1)
addY(l - 1);
if (y[r + 1] == 0 && r < m)
addY(r);
}
write(tot);
putchar('\n');
}
return 0;
}