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)\),则贡献就是:

\[\begin{aligned} f(n,m) &= \sum_{k=1}^n(n-k+1)(m - k + 1)\\ &= \sum_{k=1}^n(k^2 + (m - n)k)\\ &= \frac{n(n+1)(2n+1)}{6}+(m - n)\frac{n(n+1)}{2} \end{aligned} \]

这个推导过程可以直接全拆开再合并,反正最终能得到这个结果。

我们考虑四个数列 \(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\),则答案为:

\[\sum_{i=1}^k\sum_{j=1}^lf(n_i,m_j) \]

对于固定的 \(x\),我们考虑怎么求 \(\sum_{i=1}^k f(x,m_i)\),按照上面的拆开就是:

\[\sum_{i=1}^k f(x,m_i) = a_x\sum_{m_i \le x}d_{m_i} + b_x\sum_{m_i \le x}c_{m_i} + c_x\sum_{m_i > x}b_{m_i} + d_x\sum_{m_i > x}a_{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;
}
posted @ 2024-02-12 22:02  rlc202204  阅读(50)  评论(0编辑  收藏  举报