【LOJ3059】「HNOI2019」序列

【题目链接】

  • 点击打开链接

【思路要点】

  • 没有修改的做法在《IOI2018中国国家候选队论文集——浅谈保序回归问题》中有所介绍。
  • 具体做法如下:
    ( 1 ) (1) (1) 、注意到若所有 B i B_i Bi 均相等,最小化 ∑ i = 1 N ( A i − B ) 2 = ∑ i = 1 N A i 2 − 2 A i B + B 2 \sum_{i=1}^{N}(A_i-B)^2=\sum_{i=1}^{N}A_i^2-2A_iB+B^2 i=1N(AiB)2=i=1NAi22AiB+B2 B B B 应当满足 B = ∑ i = 1 N A i N B=\frac{\sum_{i=1}^{N}A_i}{N} B=Ni=1NAi ,即集合内所有 A i A_i Ai 的平均数。
    ( 2 ) (2) (2) 、最优解的形式一定为分成最少的若干段,每一段的 B i B_i Bi 即取其中 A i A_i Ai 的平均数,同时保证 B B B 数组的有序性,其证明可参见论文。
    ( 3 ) (3) (3) 、可以直接使用单调栈维护各段的平均数。
  • 对于带有修改的版本,可以先将修改离线,前后各做一次单调栈,这样,对于一个询问 ( x , k ) (x,k) (x,k) ,我们便只需要合并 1 ∼ x − 1 , x , x + 1 ∼ N 1\sim x-1,x,x+1\sim N 1x1,x,x+1N 即可。
  • 注意到单调栈中的一段数的任何前缀平均值都大于去掉该前缀剩余的数的平均值,最终与 x x x 分到同一段的一定是一系列原先单调栈中的整段。
  • 同时,考虑没有修改时做法的过程, x x x 会与尽可能少的 1 ∼ x − 1 1\sim x-1 1x1 中的段合并,使得后续 B i B_i Bi 有序。因此,可以二分出 x x x 1 ∼ x − 1 1\sim x-1 1x1 中合并的段数,再用一个在 x + 1 ∼ N x+1\sim N x+1N 对应的单调栈中的二分判断之。
  • 时间复杂度 O ( N + M L o g 2 N ) O(N+MLog^2N) O(N+MLog2N)

【代码】

#include
using namespace std;
const int MAXN = 2e5 + 5;
const int P = 998244353;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, m, a[MAXN], inv[MAXN], qs[MAXN], ans[MAXN]; ll s[MAXN];
vector <pair <int, int>> qrys[MAXN], del[MAXN];
int top; pair <int, int> p[MAXN];
int toq; pair <int, int> q[MAXN];
int power(int x, int y) {
	if (y == 0) return 1;
	int tmp = power(x, y / 2);
	if (y % 2 == 0) return 1ll * tmp * tmp % P;
	else return 1ll * tmp * tmp % P * x % P;
}
int cost(int l, int r) {
	int sum = (s[r] - s[l - 1]) % P, len = r - l + 1;
	int ind = 1ll * sum * inv[len] % P;
	return (1ll * ind * ind % P * len % P - 2ll * sum * ind % P + P + qs[r] - qs[l - 1] + P) % P;
}
int cost(int add, int l, int r) {
	int sum = (s[r] - s[l - 1] + add) % P, len = r - l + 1;
	int ind = 1ll * sum * inv[len] % P;
	return (1ll * ind * ind % P * len % P - 2ll * sum * ind % P + P + qs[r] - qs[l - 1] + P) % P;
}
bool Greater(int add, int la, int ra, int lb, int rb) {
	return (s[ra] - s[la - 1] + add) * (rb - lb + 1) > (s[rb] - s[lb - 1]) * (ra - la + 1);
}
bool Lesser(int add, int la, int ra, int lb, int rb) {
	return (s[ra] - s[la - 1] + add) * (rb - lb + 1) < (s[rb] - s[lb - 1]) * (ra - la + 1);
}
int querypos(int pos, int add) {
	int l = 1, r = toq + 1;
	while (l < r) {
		int mid = (l + r + 1) / 2;
		if (Greater(add, pos, q[mid - 1].first - 1, q[mid - 1].first, q[mid - 2].first - 1)) r = mid - 1;
		else l = mid;
	}
	return q[l - 1].first - 1;
}
int querysum(int pos, int add) {
	int l = 1, r = toq + 1;
	while (l < r) {
		int mid = (l + r + 1) / 2;
		if (Greater(add, pos, q[mid - 1].first - 1, q[mid - 1].first, q[mid - 2].first - 1)) r = mid - 1;
		else l = mid;
	}
	return (q[l - 1].second + cost(add, pos, q[l - 1].first - 1)) % P;
}
int query(int pos, int add) {
	int l = 1, r = top + 1;
	while (l < r) {
		int mid = (l + r + 1) / 2;
		int tmp = querypos(p[mid - 1].first + 1, add);
		if (Lesser(add, p[mid - 1].first + 1, tmp, p[mid - 2].first + 1, p[mid - 1].first)) r = mid - 1;
		else l = mid;
	}
	return (0ll + p[l - 1].second + querysum(p[l - 1].first + 1, add) - 1ll * a[pos] * a[pos] % P + P + 1ll * (a[pos] + add) * (a[pos] + add)) % P;
}
int main() {
	read(n), read(m);
	for (int i = 1; i <= n; i++) {
		read(a[i]), s[i] = s[i - 1] + a[i];
		qs[i] = (qs[i - 1] + 1ll * a[i] * a[i]) % P;
		inv[i] = power(i, P - 2);
	}
	for (int i = 1; i <= m; i++) {
		int x, y; read(x), read(y);
		qrys[x].emplace_back(y, i);
	}
	for (int i = 1; i <= n; i++) {
		while (top >= 1 && (s[i] - s[p[top].first]) * (p[top].first - p[top - 1].first) < (s[p[top].first] - s[p[top - 1].first]) * (i - p[top].first)) del[i].push_back(p[top--]);
		p[top + 1] = make_pair(i, (p[top].second + cost(p[top].first + 1, i)) % P), top++;
	}
	writeln(p[top].second);
	q[0].first = n + 1;
	for (int i = n; i >= 1; i--) {
		top--;
		while (!del[i].empty()) {
			p[++top] = del[i].back();
			del[i].pop_back();
		}
		for (auto x : qrys[i])
			ans[x.second] = query(i, x.first - a[i]);
		while (toq >= 1 && (s[q[toq].first - 1] - s[i - 1]) * (q[toq - 1].first - q[toq].first) > (s[q[toq - 1].first - 1] - s[q[toq].first - 1]) * (q[toq].first - i)) toq--;
		q[toq + 1] = make_pair(i, (q[toq].second + cost(i, q[toq].first - 1)) % P), toq++;
	}
	for (int i = 1; i <= m; i++)
		writeln(ans[i]);
	return 0;
}

你可能感兴趣的:(【OJ】LOJ,【类型】做题记录,【算法】保序回归问题,【数据结构】栈与单调栈,【算法】倍增与二分,【算法】离线操作)