【集训队作业】IOI 2020 集训队作业 试题泛做 9

Codeforces 566C Logistical Questions

考虑如下做法:选择一条边 ( x , y ) (x,y) ,计算 c o s t x , c o s t y cost_x,cost_y ,若 c o s t x cost_x 较小,则只考虑将中心设置在 x x 所在的子树,否则,只考虑将中心设置在 y y 所在的子树。

考虑其正确性,首先,移除必须在节点处设置中心的限制,我们认为任何地方都可以设置中心。

对于树上的任意一条路径,各个点关于中心坐标的代价是一个凸函数,这意味着其导数是单调的,若干个单调的函数相加时,得到的依然是单调函数。

因此,对于树上的任意一条路径,存在唯一的最优解,从而上述算法是正确的。

那么,对原树进行边分治即可,边分治新增的边长可以看做一个趋于零的数 δ x \delta x ,两边 c o s t cost 相同时,可以比较两侧的导数。

时间复杂度为 O ( N L o g N ) O(NLogN)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 4e5 + 5;
const double eps = 1e-11;
typedef long long ll;
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;
}
int n, tot, w[MAXN], size[MAXN], dist[MAXN];
int belong[MAXN], x[MAXN], y[MAXN], l[MAXN];
vector <pair <int, int>> a[MAXN], b[MAXN];
bool side[MAXN], val[MAXN];
void dfs(int pos, int fa, bool col) {
	size[pos] = val[pos];
	side[pos] = col;
	for (auto x : a[pos])
		if (x.first != fa) {
			dist[x.first] = dist[pos] + x.second;
			dfs(x.first, pos, col);
			size[pos] += size[x.first];
		}
}
bool judge(int x, int y) {
	dist[x] = 0, dfs(x, 0, true);
	double costx = 0, costy = 0;
	for (int i = 1; i <= tot; i++)
		costx += w[i] * sqrt(dist[i]) * dist[i];
	dist[y] = 0, dfs(y, 0, false);
	for (int i = 1; i <= tot; i++)
		costy += w[i] * sqrt(dist[i]) * dist[i];
	if (fabs(costx - costy) / fabs(costx) >= eps) {
		if (costx > costy) return false;
		else return true;
	}
	dist[x] = 0, dfs(x, y, true);
	dist[y] = 0, dfs(y, x, false);
	double derx = 0, dery = 0;
	for (int i = 1; i <= tot; i++)
		if (side[i]) dery += w[i] * sqrt(dist[i]);
		else derx += w[i] * sqrt(dist[i]);
	return derx < dery;
}
void col(int pos, int fa) {
	val[pos] = false;
	for (auto x : a[pos])
		if (x.first != fa) col(x.first, pos);
}
int findans(int cnt) {
	while (cnt != 1) {
		pair <int, int> e; int div = cnt;
		dfs(1, 0, true);
		for (int i = 1; i <= tot; i++)
		for (auto x : a[i]) {
			int tmp = min(size[i], size[x.first]);
			chkmax(tmp, cnt - tmp);
			if (tmp < div) {
				div = tmp;
				e = make_pair(i, x.first);
			}
		}
		if (judge(e.first, e.second)) col(e.second, e.first);
		else col(e.first, e.second);
		cnt = 0;
		for (int i = 1; i <= tot; i++)
			cnt += val[i];
	}
	for (int i = 1; i <= tot; i++)
		if (val[i]) return i;
	assert(false);
	return -1;
}
void addedge(int x, int y, int z) {
	a[x].emplace_back(y, z);
	a[y].emplace_back(x, z);
}
void rebuild(int pos, int fa) {
	int last = 0, len = 0;
	for (auto x : b[pos]) 
		if (x.first != fa) {
			rebuild(x.first, pos);
			if (last == 0) last = x.first, len = x.second;
			else {
				addedge(++tot, last, len);
				addedge(tot, x.first, x.second);
				belong[tot] = pos;
				last = tot, len = 0;
			}
		}
	if (last) addedge(pos, last, len);
}
int main() {
	read(n), tot = n;
	for (int i = 1; i <= n; i++)
		read(w[i]), belong[i] = i;
	for (int i = 1; i <= n - 1; i++) {
		read(x[i]), read(y[i]), read(l[i]);
		b[x[i]].push_back(make_pair(y[i], l[i]));
		b[y[i]].push_back(make_pair(x[i], l[i]));
	}
	rebuild(1, 0);
	for (int i = 1; i <= tot; i++)
		val[i] = true;
	int ans = findans(tot); double bns = 0;
	dist[ans] = 0, dfs(ans, 0, true);
	for (int i = 1; i <= tot; i++)
		bns += w[i] * sqrt(dist[i]) * dist[i];
	printf("%d %.10lf\n", belong[ans], bns);
	return 0;
}

Codeforces 566E Restoring Map

当树是菊花时,所有点的集合均为全集,可以特判。

考虑找到树上的一个叶子结点以及与其相连的一条边,把它们删去,迭代 N N 次。

叶子节点对应的集合一定被与其相邻的一个点的集合包含,并且,满足该条件的大小最小的集合也一定是叶子节点对应的集合,考虑首先找到大小最小的,存在另一个集合包含它的集合 S S

S S 中包含了到该叶子节点距离在 0 , 1 , 2 0,1,2 中的数,取其中在所有集合中出现次数最少的点作为所找到的叶子 x x ,本部分的复杂度可以通过只枚举树上相邻点的集合,即交集大小超过 3 3 的集合来保证。

考虑找到与 x x 相连的点 y y 的候选集合 T T ,令 T T 为所有与 S S 交集在 2 2 以上的集合的交集。

可以证明, T T 的大小不超过 2 2 ,分类讨论几类情况可以发现,当且仅当树的形态是中心相邻的两个菊花, T = 2 |T|=2 ,且无法区分 x x T T 中的哪一个元素相邻。

那么,找到菊花的中心,将剩余的点接在中心上即可。注意将 x x 接在某个点 y y 上时,须增加 “ y y 不能与 S x S_x 中不存在的点相邻” 的限制,用于判断在菊花中心处的拼接。

时间复杂度 O ( N 3 w ) O(\frac{N^3}{w})

题解中的做法提到了一种更简单的思路:若两个集合的交集为 2 2 ,则我们可以确定树上的一条边,这样我们可以找到所有非叶节点之间的边。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
typedef long long ll;
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;
}
int n, cnt[MAXN];
vector <int> adj[MAXN];
bitset <MAXN> a[MAXN], d[MAXN];
bool visa[MAXN], mark[MAXN];
void solve(int lft) {
	bitset <MAXN> lst; lst.set();
	for (int i = 1; i <= n; i++)
		if (!visa[i]) {
			bool flg = false;
			for (auto x : adj[i])
				if ((a[x] & a[i]) == a[i]) {
					flg = true;
					break;
				}
			if (flg && a[i].count() < lst.count()) lst = a[i];
		}
	int Min = n + 1, pos = 0;
	for (int i = 1; i <= n; i++)
		if (!mark[i] && lst[i] && cnt[i] < Min) {
			Min = cnt[i];
			pos = i;
		}
	if (Min == lft) {
		for (int i = 1; i <= n; i++)
			if (!mark[i] && i != pos) printf("%d %d\n", i, pos);
		return;
	}
	bitset <MAXN> tmp; tmp.set();
	for (int i = 1; i <= n; i++)
		if (!visa[i] && a[i][pos]) tmp &= a[i];
	tmp.reset(pos);
	bitset <MAXN> tnp = tmp;
	for (int i = 1; i <= n; i++)
		if (!visa[i] && (tnp & a[i]).count() >= 2) tmp &= a[i];
	assert(tmp.count() == 1 || tmp.count() == 2);
	int res = tmp._Find_first();
	if (tmp.count() == 2) {
		int x = res, y = tmp._Find_next(x); bool flg = false;
		for (int i = 1; i <= n; i++)
			if (!visa[i] && !a[i][pos]) {
				if (a[i][y] ^ a[i][x]) {
					flg = true;
					if (a[i][y]) res = x;
					else res = y;
				}
			}
		if (!flg) {
			printf("%d %d\n", x, y);
			mark[x] = mark[y] = true;
			int val = 0;
			for (int i = 1; i <= n; i++)
				if (!mark[i]) val = i;
			vector <int> s, t;
			for (int i = 1; i <= n; i++)
				if (!visa[i] && a[i].count() < lft && a[i][val]) {
					for (int j = a[i]._Find_first(); j <= n; j = a[i]._Find_next(j)) {
						if (!mark[j]) {
							mark[j] = true;
							s.push_back(j);
						}
					}
					break;
				}
			for (int i = 1; i <= n; i++)
				if (!mark[i]) t.push_back(i);
			for (auto v : s) if (d[v][x]) swap(x, y);
			for (auto v : t) if (d[v][y]) swap(x, y);
			for (auto v : s) printf("%d %d\n", x, v);
			for (auto v : t) printf("%d %d\n", y, v);
			return;
		}
	}
	printf("%d %d\n", pos, res);
	int home = 0;
	mark[pos] = true, cnt[pos] = 0;
	for (int i = 1; i <= n; i++)
		if (!visa[i] && a[i][pos]) {
			a[i].reset(pos);
			if (home == 0 || a[i].count() <= a[home].count()) home = i;
		}
	visa[home] = true;
	for (int i = 1; i <= n; i++)
		if (a[home][i]) cnt[i]--;
	d[res] |= ~a[home];
	solve(lft - 1);
}
int main() {
	read(n);
	for (int i = 1; i <= n; i++) {
		int x; read(x);
		while (x--) {
			int y; read(y), cnt[y]++;
			a[i].set(y);
		}
	}
	for (int i = 1; i <= n; i++)
	for (int j = i + 1; j <= n; j++) {
		if ((a[i] & a[j]).count() >= 4) {
			adj[i].push_back(j);
			adj[j].push_back(i);
		}
		if ((a[i] & a[j]) == a[i]) adj[i].push_back(i);
		if ((a[i] & a[j]) == a[j]) adj[j].push_back(j);
	}
	solve(n);
	return 0;
}

Codeforces 568C New Language

逐位确定答案,并用 2-SAT 判断可行性即可。

时间复杂度 O ( N 3 + M ) O(N^3+M)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 405;
typedef long long ll;
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;
}
char s[MAXN], lim[MAXN], ans[MAXN];
int n, m, l, edge[MAXN][MAXN], tot, point[MAXN][2];
int timer, dfn[MAXN], low[MAXN], cnt, belong[MAXN];
int top, Stack[MAXN]; bool instack[MAXN];
void work(int pos) {
	dfn[pos] = low[pos] = ++timer;
	Stack[++top] = pos, instack[pos] = true;
	for (int i = 1; i <= tot; i++)
		if (edge[pos][i]) {
			if (dfn[i] == 0) {
				work(i);
				chkmin(low[pos], low[i]);
			} else if (instack[i]) chkmin(low[pos], dfn[i]);
		}
	if (low[pos] == dfn[pos]) {
		int tmp = Stack[top--];
		belong[tmp] = ++cnt;
		instack[tmp] = false;
		while (tmp != pos) {
			tmp = Stack[top--];
			belong[tmp] = cnt;
			instack[tmp] = false;
		}
	}
}
bool check() {
	timer = cnt = 0, memset(dfn, 0, sizeof(dfn));
	for (int i = 1; i <= tot; i++)
		if (dfn[i] == 0) work(i);
	for (int i = 1; i <= n; i++)
		if (belong[point[i][0]] == belong[point[i][1]]) return false;
	return true;
}
void limit(int pos, bool type, int d) {
	edge[point[pos][type ^ 1]][point[pos][type]] += d;
}
int main() {
	scanf("\n%s", s + 1);
	l = strlen(s + 1), read(n), read(m);
	int cnt[2] = {0, 0};
	for (int i = 1; i <= l; i++)
		cnt[s[i] == 'V']++;
	for (int i = 1; i <= n; i++) {
		point[i][0] = ++tot;
		point[i][1] = ++tot;
	}
	if (cnt[0] == 0) {
		for (int i = 1; i <= n; i++)
			limit(i, 1, 1);
	}
	if (cnt[1] == 0) {
		for (int i = 1; i <= n; i++)
			limit(i, 0, 1);
	}
	for (int i = 1; i <= m; i++) {
		int x, y; char tx, ty;
		scanf("%d %c %d %c", &x, &tx, &y, &ty);
		edge[point[x][tx == 'V']][point[y][ty == 'V']]++;
		edge[point[y][ty != 'V']][point[x][tx != 'V']]++;
	}
	scanf("\n%s", lim + 1);
	for (int i = 1; i <= n; i++)
		limit(i, s[lim[i] - 'a' + 1] == 'V', 1);
	if (check()) {
		printf("%s\n", lim + 1);
		return 0;
	}
	for (int i = 1; i <= n; i++)
		limit(i, s[lim[i] - 'a' + 1] == 'V', -1);
	for (int p = n; p >= 1; p--) {
		for (int i = 1; i <= p - 1; i++)
			limit(i, s[lim[i] - 'a' + 1] == 'V', 1);
		int q[2] = {l + 1, l + 1};
		for (int i = l; i >= lim[p] - 'a' + 2; i--)
			q[s[i] == 'V'] = i;
		int nxt = l + 1;
		limit(p, 0, 1);
		if (q[0] != l + 1 && check()) chkmin(nxt, q[0]);
		limit(p, 0, -1);
		limit(p, 1, 1);
		if (q[1] != l + 1 && check()) chkmin(nxt, q[1]);
		limit(p, 1, -1);
		if (nxt != l + 1) {
			for (int i = 1; i <= p - 1; i++)
				ans[i] = lim[i];
			ans[p] = nxt + 'a' - 1;
			limit(p, s[nxt] == 'V', 1);
			int r[2] = {l + 1, l + 1};
			for (int i = l; i >= 1; i--)
				r[s[i] == 'V'] = i;
			for (int i = p + 1; i <= n; i++) {
				ans[i] = l + 'a';
				limit(i, 0, 1);
				if (r[0] != l + 1 && check()) chkmin(ans[i], (char) (r[0] + 'a' - 1));
				limit(i, 0, -1);
				limit(i, 1, 1);
				if (r[1] != l + 1 && check()) chkmin(ans[i], (char) (r[1] + 'a' - 1));
				limit(i, 1, -1);
				limit(i, s[ans[i] - 'a' + 1] == 'V', 1);
			}
			printf("%s\n", ans + 1);
			return 0;
		}
		for (int i = 1; i <= p - 1; i++)
			limit(i, s[lim[i] - 'a' + 1] == 'V', -1);
	}
	puts("-1");
	return 0;
}

Codeforces 568E Longest Increasing Subsequence

考虑首先填入作为最长上升子序列出现的元素,再任意填入剩余元素。

那么,可以认为,填入的数字是两两不同的。

考虑对于 a i a_i 1 -1 的位置,记 d p i dp_i 表示以 a i a_i 结尾的最长上升子序列的长度。

转移时考虑枚举 a i a_i 1 -1 的个数 c n t cnt ,则显然应当取最近的 c n t cnt 1 -1 填入恰小于 a i a_i c n t cnt 个数,再取满足 a x a_x 小于所填入的第一个数 y y d p x dp_x 的最大值 M a x y Max_y 更新答案。

那么,从前到后进行 DP ,中途维护 M a x y Max_y ,支持 O ( 1 ) O(1) 询问,每当遇到 1 -1 便进行转移即可。

N , M N,M 同阶,用分块维护 M a x y Max_y ,时间复杂度 O ( N N + N K ) O(N\sqrt{N}+NK)

若注意到 M a x y Max_y 的递增性,可以优化至 O ( N L o g N + N K ) O(NLogN+NK)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
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;
}
namespace Blocks {
	const int MAXN = 2e5 + 5;
	int n, block, tot, l[MAXN], r[MAXN], belong[MAXN];
	pair <int, int> pre[MAXN], part[MAXN];
	void init(int x) {
		n = x, block = sqrt(x);
		for (int i = 1; i <= n; i++) {
			if (i % block == 1 % block) l[++tot] = i;
			r[tot] = i, belong[i] = tot;
		}
	}
	void modify(int x, pair <int, int> val) {
		for (int i = x; i <= r[belong[x]]; i++)
			chkmax(part[i], val);
		for (int i = belong[x]; i <= tot; i++)
			chkmax(pre[i], val);
	}
	pair <int, int> query(int x) {
		return max(pre[belong[x] - 1], part[x]);
	}
}
set <int> st;
map <int, int> rnk, home;
int dp[MAXN], from[MAXN], type[MAXN], lft[MAXN];
int n, m, k, tot, a[MAXN], b[MAXN], c[MAXN], bak[MAXN];
int main() {
	read(n);
	for (int i = 1; i <= n; i++) {
		read(a[i]);
		if (a[i] != -1) st.insert(a[i]);
	}
	read(m);
	for (int i = 1; i <= m; i++) {
		read(b[i]);
		st.insert(b[i]);
	}
	sort(b + 1, b + m + 1);
	for (auto x : st) {
		rnk[x] = ++tot;
		home[tot] = x;
	}
	for (int i = 1; i <= n; i++)
		if (a[i] != -1) a[i] = rnk[a[i]];
	for (int i = 1; i <= m; i++) {
		b[i] = rnk[b[i]], lft[b[i]]++;
		if (b[i] != c[k]) c[++k] = b[i];
	}
	a[n + 1] = tot + 1;
	for (int i = 1; i <= n + 1; i++)
		if (a[i] != -1) bak[i] = lower_bound(c + 1, c + k + 1, a[i]) - c;
	Blocks :: init(tot);
	for (int i = 1; i <= n + 1; i++) {
		if (a[i] == -1) {
			int cnt = 0;
			for (int j = i; j <= n + 1; j++)
				if (a[j] == -1) cnt++;
				else if (bak[j] > cnt) {
					pair <int, int> tmp = Blocks :: query(c[bak[j] - cnt] - 1);
					if (tmp.first + cnt + 1 > dp[j]) {
						dp[j] = tmp.first + cnt + 1;
						from[j] = tmp.second;
						type[j] = cnt;
					}
				}
		} else {
			pair <int, int> tmp = Blocks :: query(a[i] - 1);
			if (tmp.first + 1 > dp[i]) {
				dp[i] = tmp.first + 1;
				from[i] = tmp.second;
				type[i] = 0;
			}
			Blocks :: modify(a[i], make_pair(dp[i], i));
		}
	}
	int now = n + 1;
	while (now != 0) {
		int cnt = type[now], pos = bak[now];
		for (int i = now; cnt; i--)
			if (a[i] == -1) {
				a[i] = c[--pos];
				lft[c[pos]]--;
				cnt--;
			}
		now = from[now];
	}
	int tmp = 1;
	for (int i = 1; i <= n; i++)
		if (a[i] == -1) {
			while (lft[tmp] == 0) tmp++;
			lft[tmp]--, a[i] = tmp;
		}
	for (int i = 1; i <= n; i++)
		printf("%d ", home[a[i]]);
	return 0;
}

Codeforces 571D Campus

离线询问,考虑对于每个询问 Q   x Q\ x ,计算 x x 上次被清空的时刻,然后不计清空操作,计算两个时刻人数的差值。

计算 x x 上次被清空的时刻,以及不计清空操作维护人数都可以通过启发式合并 vector 完成。

时间复杂度 O ( N L o g N + M ) O(NLogN+M)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5e5 + 5;
typedef long long ll;
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("");
}
char opt[MAXN];
int n, m, x[MAXN], y[MAXN]; ll ans[MAXN];
vector <pair <int, int>> mns[MAXN];

vector <int> a[MAXN]; int tag[MAXN];
pair <int, int> last[MAXN]; int f[MAXN];
ll all[MAXN], self[MAXN];

int find(int x) {
	if (f[x] == x) return x;
	else return f[x] = find(f[x]);
}
int queryTimer(int x) {
	int y = find(x);
	if (tag[y] > last[x].first) return tag[y];
	else return last[x].second;
}
ll querySum(int x) {
	int y = find(x);
	return all[y] + self[x];
}
int main() {
	read(n), read(m);
	for (int i = 1; i <= m; i++) {
		scanf("\n%c", &opt[i]);
		if (opt[i] == 'U' || opt[i] == 'M') read(x[i]), read(y[i]);
		else read(x[i]);
	}
	for (int i = 1; i <= n; i++) {
		f[i] = i, tag[i] = 0;
		last[i] = make_pair(0, 0);
		a[i].clear(), a[i].push_back(i);
	}
	for (int i = 1; i <= m; i++) {
		if (opt[i] == 'Q') mns[queryTimer(x[i])].emplace_back(x[i], i);
		if (opt[i] == 'Z') tag[find(x[i])] = i;
		if (opt[i] == 'M') {
			int tx = find(x[i]), ty = find(y[i]);
			if (a[tx].size() > a[ty].size()) swap(tx, ty);
			for (auto x : a[tx]) {
				a[ty].push_back(x);
				last[x] = make_pair(i, queryTimer(x));
			}
			f[tx] = ty;
		}
	}
	
	for (int i = 1; i <= n; i++) {
		f[i] = i, all[i] = self[i] = 0;
		a[i].clear(), a[i].push_back(i);
	}
	for (int i = 1; i <= m; i++) {
		for (auto x : mns[i]) ans[x.second] -= querySum(x.first);
		if (opt[i] == 'Q') ans[i] += querySum(x[i]);
		if (opt[i] == 'A') all[find(x[i])] += a[find(x[i])].size();
		if (opt[i] == 'U') {
			int tx = find(x[i]), ty = find(y[i]);
			if (a[tx].size() > a[ty].size()) swap(tx, ty);
			for (auto x : a[tx]) {
				a[ty].push_back(x);
				self[x] = querySum(x) - all[ty];
			}
			f[tx] = ty;
		}
	}
	
	for (int i = 1; i <= m; i++)
		if (opt[i] == 'Q') writeln(ans[i]);
	return 0;
}

Codeforces 571E Geometric Progressions

分开考虑各个质因数带来的影响,记第 i i 个等比数列 b b 的幂次为 x i x_i ,则对于各个质因数,我们都可以得到 N 2 N^2 个二元一次方程,若能够直接确定某个 x i x_i ,则直接检测其是否合法即可。

不能确定时,当且仅当各个质因数得到的 N 2 N^2 个二元一次方程本质相同。

此时,所有方程可以写成一个统一的形式 k 1 x 1 + b 1 = k 2 x 2 + b 2 = = k N x N + b N k_1x_1+b_1=k_2x_2+b_2=\dots=k_Nx_N+b_N

则可以用拓展欧几里得算法解出方程的最小解,它的级别在 1 , 2 , , 29 1,2,\dots,29 的 LCM 左右,可以用 64 位整型计算。

时间复杂度 O ( N V + N 2 L o g V ) O(N\sqrt{V}+N^2LogV)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 105;
const int MAXM = 2e3 + 5;
const int MAXV = 1e5 + 5;
const int INF  = 1e9;
const int P = 1e9 + 7;
typedef long long ll;
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;
}
struct info {ll k, b; };
void exgcd(ll a, ll b, ll &x, ll &y) {
	if (b == 0) {
		assert(a == 1);
		x = 1, y = 0;
		return;
	}
	ll q = a / b, r = a % b;
	exgcd(b, r, y, x);
	x = -x, y = -y + x * q;
}
info merge(info x, info y) {
	if (x.b > y.b) swap(x, y);
	ll g = __gcd(x.k, y.k), c = y.b - x.b;
	if (c % g != 0) {
		puts("-1");
		exit(0);
	}
	ll tx = 0, ty = 0; c /= g;
	exgcd(x.k / g, y.k / g, tx, ty);
	if (y.k <= 30) {
		ll k = y.k / g, b = (tx % k + k) * c % k;
		x.b += x.k * b, x.k *= k;
		return x;
	} else {
		ll k = x.k / g, b = (ty % k + k) * c % k;
		y.b += y.k * b, y.k *= k;
		return y;
	}
}
set <int> primes;
int n, a[MAXN], b[MAXN];
int x[MAXN], y[MAXN], tx[MAXN], ty[MAXN];
int tot, prime[MAXV], f[MAXV];
void sieve(int n) {
	for (int i = 2; i <= n; i++) {
		if (f[i] == 0) prime[++tot] = f[i] = i;
		for (int j = 1; j <= tot && prime[j] <= f[i]; j++) {
			int tmp = prime[j] * i;
			if (tmp > n) break;
			f[tmp] = prime[j];
		}
	}
}
void inprime(int x) {
	for (int i = 1; i <= tot; i++)
		while (x % prime[i] == 0) {
			x /= prime[i];
			primes.insert(prime[i]);
		}
	if (x != 1) primes.insert(x);
}
int power(int x, ll 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;
}
void report(int pos, ll val) {
	if (val < 0) {
		puts("-1");
		exit(0);
	}
	for (auto p : primes) {
		static int x[MAXN], y[MAXN];
		for (int i = 1, tmp; i <= n; i++) {
			x[i] = 0, y[i] = 0;
			tmp = a[i]; while (tmp % p == 0) tmp /= p, x[i]++;
			tmp = b[i]; while (tmp % p == 0) tmp /= p, y[i]++;
		}
		ll goal = x[pos] + y[pos] * val;
		for (int i = 1; i <= n; i++) {
			if (x[i] > goal || (y[i] == 0 && x[i] != goal) || (y[i] != 0 && (goal - x[i]) % y[i])) {
				puts("-1");
				exit(0);
			}
		}
	}
	printf("%lld\n", 1ll * a[pos] * power(b[pos], val) % P);
	exit(0);
}
int main() {
	read(n), sieve(sqrt(1e9 + 7));
	for (int i = 1; i <= n; i++) {
		read(a[i]), read(b[i]);
		inprime(a[i]);
		inprime(b[i]);
	}
	if (n == 1) {
		cout << a[1] << endl;
		return 0;
	}
	bool vis = false;
	for (auto p : primes) {
		int Min = INF;
		for (int i = 1, tmp; i <= n; i++) {
			x[i] = 0, y[i] = 0;
			tmp = a[i]; while (tmp % p == 0) tmp /= p, x[i]++;
			tmp = b[i]; while (tmp % p == 0) tmp /= p, y[i]++;
			chkmin(Min, x[i]);
		}
		int g = 0;
		for (int i = 1; i <= n; i++) {
			x[i] -= Min;
			g = __gcd(g, x[i]);
			g = __gcd(g, y[i]);
		}
		if (g == 0) continue;
		for (int i = 1; i <= n; i++) {
			x[i] /= g;
			y[i] /= g;
		}
		bool fx = true, fy = true;
		for (int i = 1; i <= n; i++) {
			if (y[i]) fy = false;
			if (x[i] != x[1]) fx = false;
		}
		if (fy) {
			if (fx) continue;
			else {
				puts("-1");
				return 0;
			}
		}
		for (int i = 1; i <= n; i++)
		for (int j = i + 1; j <= n; j++) {
			if (y[i] == 0 && y[j] != 0) report(j, (x[i] - x[j]) / y[j]);
			if (y[j] == 0 && y[i] != 0) report(i, (x[j] - x[i]) / y[i]);
		}
		if (vis) {
			for (int i = 1; i <= n; i++)
			for (int j = i + 1; j <= n; j++) {
				int k1, k2, b1, b2;
				k1 = ty[i] * y[j];
				b1 = ty[i] * (x[j] - x[i]);
				k2 = y[i] * ty[j];
				b2 = y[i] * (tx[j] - tx[i]);
				if (k1 != k2) report(j, (b1 - b2) / (k2 - k1));
				else if (b1 != b2) {
					puts("-1");
					exit(0);
				}
				k1 = ty[j] * y[i];
				b1 = ty[j] * (x[i] - x[j]);
				k2 = y[j] * ty[i];
				b2 = y[j] * (tx[i] - tx[j]);
				if (k1 != k2) report(i, (b1 - b2) / (k2 - k1));
				else if (b1 != b2) {
					puts("-1");
					exit(0);
				}
			}
		} else {
			for (int i = 1; i <= n; i++) {
				tx[i] = x[i];
				ty[i] = y[i];
			}
			vis = true;
		}
	}
	if (!vis) {
		cout << a[1] << endl;
		return 0;
	}
	info res = (info) {1, 0};
	for (int i = 1; i <= n; i++)
		res = merge(res, (info) {ty[i], tx[i] % ty[i]});
	ll ans = res.b;
	for (int i = 1; i <= n; i++)
		while (tx[i] > ans) ans += res.k;
	report(1, (ans - tx[1]) / ty[1]);
	return 0;
}

Codeforces 573E Bear and Bowling

贪心思路

由于这是一个在序列上的最优化问题,采用贪心的策略往往能够得到不错的结果。

考虑依次向已经选择的子序列中加入一个数 a i a_i 的过程,如果在 i i 的左侧已经有 k i 1 k_i-1 个数被选取,并且 i i 的右侧被选取的数之和为 s u f i suf_i ,那么,加入 a i a_i 将会带来 k i a i + s u f i k_i\cdot a_i+suf_i 的贡献,不妨记加入 a i a_i 的贡献为 Q i Q_i

一个自然的贪心算法就出现了:我们每次选择 Q i Q_i 最大的 a i a_i 加入答案,同时更新其余尚未加入答案的 Q j Q_j ,直到所有元素都被加入答案,取贡献总和的历史最大值作为答案。

在下一节中,我们将证明这个算法的正确性。

正确性证明

为了证明上述算法的正确性,我们首先需要证明如下引理。

**引理 1:**如果 a i > a j a_i>a_j ,并且 i < j i<j ,那么 a i a_i 一定比 a j a_j 先被选取。

**证明:**考虑上述引理首次不满足位置,即使得二元组 ( i , j ) (i,j) 字典序最小的不满足的位置。如果 a i , a j a_i,a_j 之间没有元素被选中,那么 k i = k j , s u f i = s u f j k_i=k_j,suf_i=suf_j ,因此 Q i = k i a i + s u f i > Q j = k j a j + s u f j Q_i=k_i\cdot a_i+suf_i>Q_j=k_j\cdot a_j+suf_j a i a_i 会先被选取。对于 a i , a j a_i,a_j 之间每一个已经选中的元素 a x a_x ,其对 Q i Q_i 的贡献为 a x a_x ,对 Q j Q_j 的贡献为 a j a_j 。又因为 ( i , j ) (i,j) 是引理首次不满足位置,而 i < x i<x ,因此必然有 a x a i a_x\geq a_i ,从而 a x > a j a_x>a_j Q i Q_i 的增加量始终大于 Q j Q_j 的增加量,所以 Q i > Q j Q_i>Q_j ,引理得证。

**定理 1:**每次选择 Q i Q_i 最大的 a i a_i 加入答案,同时更新其余尚未加入答案的 Q j Q_j ,得到的每一个大小的序列都是对应大小的最优解。

**证明:**假设定理不成立,那么,必然存在一个最早的时刻,使得我们选择了某个元素 a i a_i ,此后,对于某个大小 s s ,我们无法通过选择更多的元素来得到 s s 的最优解,无论我们在选择更多元素时使用的策略如何。令集合 A A 表示该时刻之前已经选取的元素集合,那么,集合 A { a j } A\cup\{a_j\} 不是任何一个大小为 s s 的最优解的子集。但是,存在一个集合 B   ( A B = ) B\ (A\cap B=\emptyset) ,使得 A B = s |A\cup B|=s ,并且, A B |A\cup B| 是一个大小为 s s 的最优解,注意 B B 应当不为空。

情况 1: B B 中包含至少一个 a i a_i 左侧的元素

a j a_j 表示 B B 中最靠右侧的一个使得 j < i j<i 的元素。由于我们的贪心策略会首先选取 a i a_i ,由引理 1 ,我们知道 a i a j a_i\geq a_j 。我们将证明将 A B A\cup B 中的 a j a_j 替换为 a i a_i 不会使得方案变得更劣,从而选择 a i a_i 是合理的。

在选择 a i a_i 时, a i a_i 具有最大的 Q i Q_i ,因此 Q i Q j Q_i\geq Q_j 。考虑将 B B 中除 a j a_j 以外的元素加入 A A 中,并比较此时 Q i , Q j Q_i,Q_j 的大小。对于每一个 a x   ( x > i ) a_x\ (x>i) ,其对 Q i , Q j Q_i,Q_j 的贡献均为 a x a_x ,对于每一个 a x   ( x < j ) a_x\ (x<j) ,其对 Q i Q_i 的贡献均为 a i a_i ,对 Q j Q_j 的贡献均为 a j a_j ,并且,由 a j a_j 的选择方式,不存在 j < x < i j<x<i a x a_x 。因此, Q i Q_i 的增加量始终大于 Q j Q_j 的增加量, Q i > Q j Q_i>Q_j ,将 A B A\cup B 中的 a j a_j 替换为 a i a_i 不会使得方案变得更劣。

情况 2: B B 中的所有元素都在 a i a_i 的右侧

a j a_j 表示 B B 中最靠左侧的一个元素,由于 B B 中的所有元素都在 a i a_i 的右侧,有 j > i j>i 。我们将证明将 A B A\cup B 中的 a j a_j 替换为 a i a_i 不会使得方案变得更劣,从而选择 a i a_i 是合理的。

在选择 a i a_i 时, a i a_i 具有最大的 Q i Q_i ,因此 Q i Q j Q_i\geq Q_j 。同样考虑将 B B 中除 a j a_j 以外的元素加入 A A 中,并比较此时 Q i , Q j Q_i,Q_j 的大小。对于每一个 a x   ( x > j ) a_x\ (x>j) ,其对 Q i , Q j Q_i,Q_j 的贡献均为 a x a_x ,并且,由 a j a_j 的选择方式,不存在 x < j x<j a x a_x 。因此, Q i Q_i 的增加量始终大于 Q j Q_j 的增加量, Q i > Q j Q_i>Q_j ,将 A B A\cup B 中的 a j a_j 替换为 a i a_i 不会使得方案变得更劣。

综上所述,假设不成立,从而定理 1 得证。

定理 1 的成立直接保证了上述贪策略的正确性,从而也导出了一个直接应用上述贪心策略的 O ( N 2 ) O(N^2) 算法,在下一节中,我们会讨论如何快速实现这个算法。

分块解法 O ( N N L o g N ) O(N\sqrt{N}LogN)

考虑向答案中加入 a i a_i Q Q 数组的影响,对于 j < i j<i a i a_i Q j Q_j 的贡献为 a i a_i ,而对于 j > i j>i a i a_i Q j Q_j 的贡献为 a j a_j ,因此,我们需要一个能够支持区间加,区间加对应 a i a_i ,以及询问全局最大值的数据结构。

考虑用分块维护这个结构,我们将每 O ( N ) O(\sqrt{N}) 个元素分为一块,在向答案中加入 a i a_i 时,对于 a i a_i 所在块左侧的块,进行整块加常数操作;对于 a i a_i 所在块右侧的块,进行整块加对应 a i a_i 操作,由此,我们需要在改变 k k 的情况下维护若干一次函数 k a i + b i k\cdot a_i+b_i 的最大值,可以通过在凸包上二分解决;对于 a i a_i 所在的块,可以暴力重建凸包。

每次向答案中加入 a i a_i 后,我们都需要重构一个大小为 O ( N ) O(\sqrt{N}) 的凸包,并且,在 O ( N ) O(\sqrt{N}) 个凸包上进行二分,这两部分的总时间复杂度均为 O ( N N L o g N ) O(N\sqrt{N}LogN)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const long long INF = 1e18;
typedef long long ll;
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;
}
ll b[MAXN], add[MAXN];
int n, tot, block, a[MAXN]; bool vis[MAXN];
int belong[MAXN], l[MAXN], r[MAXN];
int ql[MAXN], qr[MAXN], q[MAXN], k[MAXN];
inline bool exclude(int x, int y) {
	return a[x] == a[y] && b[x] >= b[y];
}
inline bool exclude(int x, int y, int z) {
	return (a[y] - a[z]) * (b[x] - b[z]) - (a[x] - a[z]) * (b[y] - b[z]) >= 0;
}
void rebuild(int pos) {
	vector <int> points;
	for (int i = l[pos]; i <= r[pos]; i++)
		if (!vis[i]) {
			points.push_back(i);
			b[i] += add[pos] + 1ll * a[i] * k[pos];
		}
	k[pos] = add[pos] = 0;
	sort(points.begin(), points.end(), [&] (int x, int y) {
		if (a[x] != a[y]) return a[x] < a[y];
		else return x < y;
	});
	ql[pos] = l[pos], qr[pos] = l[pos] - 1;
	for (auto x : points) {
		if (ql[pos] <= qr[pos] && exclude(q[qr[pos]], x)) continue;
		while (ql[pos] <= qr[pos] && exclude(x, q[qr[pos]])) qr[pos]--;
		while (ql[pos] + 1 <= qr[pos] && exclude(x, q[qr[pos]], q[qr[pos] - 1])) qr[pos]--;
		q[++qr[pos]] = x;
	}
}
pair <ll, int> find(int pos) {
	int l = ql[pos], r = qr[pos];
	if (l > r) return make_pair(-INF, 0);
	pair <ll, int> res = make_pair(-INF, 0);
	while (l < r) {
		int mid = (l + r) / 2;
		pair <ll, int> resa = make_pair(1ll * a[q[mid]] * k[pos] + b[q[mid]], q[mid]);
		pair <ll, int> resb = make_pair(1ll * a[q[mid + 1]] * k[pos] + b[q[mid + 1]], q[mid + 1]);
		if (resa > resb) {
			r = mid - 1;
			chkmax(res, resa);
		} else {
			l = mid + 2;
			chkmax(res, resb);
		}
	}
	if (l <= r) chkmax(res, make_pair(1ll * a[q[l]] * k[pos] + b[q[l]], q[l]));
	res.first += add[pos];
	return res;
}
int main() {
	read(n), block = sqrt(n);
	for (int i = 1; i <= n; i++) {
		read(a[i]), b[i] = a[i];
		if (i % block == 1 % block) l[++tot] = i;
		r[tot] = i, belong[i] = tot;
	}
	for (int i = 1; i <= tot; i++)
		rebuild(i);
	ll ans = 0, now = 0;
	for (int i = 1; i <= n; i++) {
		pair <ll, int> res = make_pair(-INF, 0);
		for (int j = 1; j <= tot; j++)
			chkmax(res, find(j));
		now += res.first;
		vis[res.second] = true;
		chkmax(ans, now);
		for (int j = 1; j < belong[res.second]; j++)
			add[j] += a[res.second];
		for (int j = l[belong[res.second]]; j < res.second; j++)
			b[j] += a[res.second];
		for (int j = res.second + 1; j <= r[belong[res.second]]; j++)
			b[j] += a[j];
		for (int j = belong[res.second] + 1; j <= tot; j++)
			k[j] += 1;
		rebuild(belong[res.second]);
	}
	cout << ans << endl;
	return 0;
}

算法优化 O ( N N + N L o g N ) O(N\sqrt{N}+NLogN)

我们可以将上述算法的 O ( L o g N ) O(LogN) 因子优化掉。

首先,由于 k a i + b i k\cdot a_i+b_i 中的系数 k k 是不断增加的,我们可以用单调指针代替二分,其均摊复杂度是 O ( 1 ) O(1) 的。其次,在重构凸包时,我们不需要每次都对点集进行排序,注意到 a i a_i 是始终不变的,我们只需要在刚开始对 a i a_i 进行一次排序即可。

由此,上述算法的时间复杂度被优化至了 O ( N N + N L o g N ) O(N\sqrt{N}+NLogN)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const long long INF = 1e18;
typedef long long ll;
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;
}
ll b[MAXN], add[MAXN];
int n, tot, block, a[MAXN]; bool vis[MAXN];
int belong[MAXN], l[MAXN], r[MAXN], p[MAXN];
int ql[MAXN], qr[MAXN], q[MAXN], k[MAXN];
inline bool exclude(int x, int y) {
	return a[x] == a[y] && b[x] >= b[y];
}
inline bool exclude(int x, int y, int z) {
	return (a[y] - a[z]) * (b[x] - b[z]) - (a[x] - a[z]) * (b[y] - b[z]) >= 0;
}
void rebuild(int pos) {
	for (int i = l[pos]; i <= r[pos]; i++)
		if (!vis[i]) b[i] += add[pos] + 1ll * a[i] * k[pos];
	k[pos] = add[pos] = 0;
	ql[pos] = l[pos], qr[pos] = l[pos] - 1;
	for (int i = l[pos]; i <= r[pos]; i++) {
		if (vis[p[i]]) continue; int x = p[i];
		if (ql[pos] <= qr[pos] && exclude(q[qr[pos]], x)) continue;
		while (ql[pos] <= qr[pos] && exclude(x, q[qr[pos]])) qr[pos]--;
		while (ql[pos] + 1 <= qr[pos] && exclude(x, q[qr[pos]], q[qr[pos] - 1])) qr[pos]--;
		q[++qr[pos]] = x;
	}
}
pair <ll, int> find(int pos) {
	int l = ql[pos], r = qr[pos];
	if (l > r) return make_pair(-INF, 0);
	pair <ll, int> res = make_pair(1ll * a[q[l]] * k[pos] + b[q[l]], q[l]);
	while (l < r) {
		pair <ll, int> tmp = make_pair(1ll * a[q[l + 1]] * k[pos] + b[q[l + 1]], q[l + 1]);
		if (tmp > res) {
			res = tmp;
			l++;
		} else break;
	}
	ql[pos] = l;
	res.first += add[pos];
	return res;
}
int main() {
	read(n), block = sqrt(n);
	for (int i = 1; i <= n; i++) {
		read(a[i]), b[i] = a[i], p[i] = i;
		if (i % block == 1 % block) l[++tot] = i;
		r[tot] = i, belong[i] = tot;
	}
	for (int i = 1; i <= tot; i++) {
		sort(p + l[i], p + r[i] + 1, [&] (int x, int y) {
			if (a[x] != a[y]) return a[x] < a[y];
			else return x < y;
		});
		rebuild(i);
	}
	ll ans = 0, now = 0;
	for (int i = 1; i <= n; i++) {
		pair <ll, int> res = make_pair(-INF, 0);
		for (int j = 1; j <= tot; j++)
			chkmax(res, find(j));
		now += res.first;
		vis[res.second] = true;
		chkmax(ans, now);
		for (int j = 1; j < belong[res.second]; j++)
			add[j] += a[res.second];
		for (int j = l[belong[res.second]]; j < res.second; j++)
			b[j] += a[res.second];
		for (int j = res.second + 1; j <= r[belong[res.second]]; j++)
			b[j] += a[j];
		for (int j = belong[res.second] + 1; j <= tot; j++)
			k[j] += 1;
		rebuild(belong[res.second]);
	}
	cout << ans << endl;
	return 0;
}

平衡树解法 O ( N L o g N ) O(NLogN)

考虑一个 O ( N 2 ) O(N^2) 的动态规划解法,记 d p i , j dp_{i,j} 表示在序列的前 i i 个元素中选择恰好 j j 个,可以产生的最大贡献,转移时,考虑是否选取 a i a_i ,则有
d p i , j = max { d p i 1 , j , d p i 1 , j 1 + j a i } dp_{i,j}=\max\{dp_{i-1,j},dp_{i-1,j-1}+j\cdot a_i\}

其中 d p i 1 , j dp_{i-1,j} 表示不取 a i a_i d p i 1 , j 1 + j a i dp_{i-1,j-1}+j\cdot a_i 表示选取 a i a_i

不妨记 S i , j S_{i,j} 表示一种使得贡献取到 d p i , j dp_{i,j} 的选取方案集合。

注意到由定理 1 , S i , j + 1 S_{i,j+1} 可以通过向 S i , j S_{i,j} 中加入一个元素得到,存在最优解 S i , j S i , j + 1 S_{i,j}\subset S_{i,j+1} ,因此,如果 a i S i , j a_i\in S_{i,j} ,则有 a i S i , j + 1 a_i\in S_{i,j+1}

这意味着使得 d p i 1 , j 1 + j a i d p i 1 , j dp_{i-1,j-1}+j\cdot a_i\geq dp_{i-1,j} j j 是一段连续的后缀

考虑用平衡树维护 d p dp 数组每一行的差分数组,在从 d p i 1 dp_{i-1} 计算 d p i dp_i 时,可以首先二分得出转移的分界位置,然后对分界位置后方的元素整体 + a i +a_i ,并在分界位置插入一个新的元素。

时间复杂度 O ( N L o g N ) O(NLogN) ,以下代码采用旋转式 Treap 实现了该算法。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
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;
}
struct Treap {
	struct Node {
		ll sum, tag, val; unsigned rnd;
		int child[2], father, size;
	} a[MAXN];
	int root, size;
	unsigned seed;
	void init() {
		srand('X' + 'Y' + 'X');
		root = size = 0;
		seed = 1258553509u;
	}
	void pushdown(int root) {
		if (a[root].tag) {
			a[a[root].child[0]].sum += a[a[root].child[0]].size * a[root].tag;
			a[a[root].child[0]].tag += a[root].tag;
			a[a[root].child[1]].sum += a[a[root].child[1]].size * a[root].tag;
			a[a[root].child[1]].tag += a[root].tag;
			a[root].val += a[root].tag;
			a[root].tag = 0;
		}
	}
	void update(int root) {
		a[root].sum += a[root].val, a[root].size = 1;
		if (a[root].child[0]) {
			a[root].sum += a[a[root].child[0]].sum;
			a[root].size += a[a[root].child[0]].size;
		}
		if (a[root].child[1]) {
			a[root].sum += a[a[root].child[1]].sum;
			a[root].size += a[a[root].child[1]].size;
		}
	}
	void add(int root, int l, int r, ll x) {
		if (l == 1 && r == a[root].size) {
			a[root].tag += x;
			a[root].sum += a[root].size * x;
			return;
		}
		pushdown(root);
		if (l <= a[a[root].child[0]].size + 1 && r >= a[a[root].child[0]].size + 1) a[root].val += x;
		if (a[root].child[0] && l <= a[a[root].child[0]].size) add(a[root].child[0], l, min(r, a[a[root].child[0]].size), x);
		l -= a[a[root].child[0]].size + 1, r -= a[a[root].child[0]].size + 1;
		if (a[root].child[1] && r >= 1) add(a[root].child[1], max(l, 1), r, x);
		update(root);
	}
	unsigned rnd() {
		seed = seed * seed + rand();
		return seed;
	}
	bool get(int x) {
		return a[a[x].father].child[1] == x;
	}
	void rotate(int x) {
		int f = a[x].father, g = a[f].father;
		pushdown(f), pushdown(x);
		bool tmp = get(x);
		a[x].father = g;
		if (g != 0) a[g].child[get(f)] = x;
		a[f].child[tmp] = a[x].child[tmp ^ 1];
		a[a[x].child[tmp ^ 1]].father = f;
		a[x].child[tmp ^ 1] = f;
		a[f].father = x;
		update(f), update(x);
	}
	void treap(int x) {
		while (a[x].father && a[x].rnd < a[a[x].father].rnd) rotate(x);
		if (a[x].father == 0) root = x;
	}
	int newnode(ll val) {
		size++;
		a[size].val = val;
		a[size].rnd = rnd();
		update(size);
		return size;
	}
	void ins(int pos, ll val) {
		if (root == 0) {
			root = newnode(val);
			return;
		}
		int x = root;
		while (true) {
			pushdown(x);
			a[x].size++, a[x].sum += val;
			if (pos <= a[a[x].child[0]].size + 1) {
				if (a[x].child[0]) x = a[x].child[0];
				else {
					a[x].child[0] = newnode(val);
					a[size].father = x;
					treap(size);
					return;
				}
			} else {
				pos -= a[a[x].child[0]].size + 1;
				if (a[x].child[1]) x = a[x].child[1];
				else {
					a[x].child[1] = newnode(val);
					a[size].father = x;
					treap(size);
					return;
				}
			}
		}
	}
	void trans(int val) {
		int res = size + 1;
		if (root != 0) {
			int x = root, cnt = 0;
			while (true) {
				pushdown(x);
				int rnk = cnt + a[a[x].child[0]].size + 1;
				if (1ll * val * rnk >= a[x].val) {
					res = rnk;
					if (a[x].child[0]) x = a[x].child[0];
					else break;
				} else {
					cnt += a[a[x].child[0]].size + 1;
					if (a[x].child[1]) x = a[x].child[1];
					else break;
				}
			}
		}
		ins(res, 1ll * res * val);
		if (res != size) add(root, res + 1, size, val);
	}
	ll ans, now;
	void getans(int root) {
		pushdown(root);
		if (a[root].child[0]) getans(a[root].child[0]);
		now += a[root].val, chkmax(ans, now);
		if (a[root].child[1]) getans(a[root].child[1]);
	}
	void getans() {
		ans = now = 0;
		getans(root);
		cout << ans << endl;
	}
} Treap;
int main() {
	int n; read(n);
	Treap.init();
	for (int i = 1; i <= n; i++) {
		int x; read(x);
		Treap.trans(x);
	}
	Treap.getans();
	return 0;
}

Codeforces 575A Fibonotci

排序特殊点,矩阵快速幂即可。

需要用线段树实现计算区间矩阵乘积。

时间复杂度 O ( N L o g N + M L o g N + M L o g M ) O(NLogN+MLogN+MLogM)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5e5 + 5;
typedef long long ll;
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;
}
struct vec {int a[2]; };
struct mat {int a[2][2]; };
int n, m, P; ll k;
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
vec operator * (vec x, mat y) {
	vec ans;
	ans.a[0] = (1ll * x.a[0] * y.a[0][0] + 1ll * x.a[1] * y.a[1][0]) % P;
	ans.a[1] = (1ll * x.a[0] * y.a[0][1] + 1ll * x.a[1] * y.a[1][1]) % P;
	return ans;
}
mat operator * (mat x, mat y) {
	mat ans;
	ans.a[0][0] = (1ll * x.a[0][0] * y.a[0][0] + 1ll * x.a[0][1] * y.a[1][0]) % P;
	ans.a[0][1] = (1ll * x.a[0][0] * y.a[0][1] + 1ll * x.a[0][1] * y.a[1][1]) % P;
	ans.a[1][0] = (1ll * x.a[1][0] * y.a[0][0] + 1ll * x.a[1][1] * y.a[1][0]) % P;
	ans.a[1][1] = (1ll * x.a[1][0] * y.a[0][1] + 1ll * x.a[1][1] * y.a[1][1]) % P;
	return ans;
}
mat make(int x, int y) {
	mat ans;
	ans.a[0][0] = 0, ans.a[0][1] = x;
	ans.a[1][0] = 1, ans.a[1][1] = y;
	return ans;
}
mat unit() {
	mat ans;
	ans.a[0][0] = ans.a[1][1] = 1;
	ans.a[1][0] = ans.a[0][1] = 0;
	return ans;
}
mat power(mat a, ll x) {
	if (x == 0) return unit();
	mat tmp = power(a, x / 2);
	if (x % 2 == 0) return tmp * tmp;
	else return tmp * tmp * a;
}
struct SegmentTree {
	struct Node {
		int lc, rc;
		mat sum;
	} a[MAXN * 2];
	int n, root, size;
	void build(int &root, int l, int r, int *c) {
		root = ++size;
		if (l == r) {
			a[root].sum = make(c[l - 1], c[l]);
			return;
		}
		int mid = (l + r) / 2;
		build(a[root].lc, l, mid, c);
		build(a[root].rc, mid + 1, r, c);
		a[root].sum = a[a[root].lc].sum * a[a[root].rc].sum;
	}
	void init(int x, int *c) {
		n = x;
		build(root, 1, n, c);
	}
	mat query(int root, int l, int r, int ql, int qr) {
		if (l == ql && r == qr) return a[root].sum;
		int mid = (l + r) / 2;
		if (mid >= qr) return query(a[root].lc, l, mid, ql, qr);
		else if (mid + 1 <= ql) return query(a[root].rc, mid + 1, r, ql, qr);
		else return query(a[root].lc, l, mid, ql, mid) * query(a[root].rc, mid + 1, r, mid + 1, qr);
	}
	mat query(int l, int r) {
		return query(root, 1, n, l, r);
	}
} ST;
int a[MAXN];
pair <ll, int> b[MAXN];
int query(ll x) {
	return a[(x - 1) % n];
}
mat query(ll l, ll r) {
	mat ans = unit();
	if (l > r) return ans; l--, r--;
	r -= (l - 1) / n * n;
	l -= (l - 1) / n * n;
	if (r <= n) return ST.query(l, r);
	int x = r % n; if (x == 0) x = n;
	ans = ans * ST.query(l, n) * power(ST.query(1, n), (r - x) / n - 1) * ST.query(1, x);
	return ans;
}
int main() {
	read(k), read(P), read(n);
	if (k <= 1) {
		cout << k % P << endl;
		return 0;
	}
	for (int i = 0; i <= n - 1; i++)
		read(a[i]); a[n] = a[0];
	ST.init(n, a);
	read(m);
	for (int i = 1; i <= m; i++)
		read(b[i].first), read(b[i].second), b[i].first++;
	b[++m] = make_pair(2e18, 0);
	sort(b + 1, b + m + 1);
	b[0] = make_pair(1, a[0]);
	vec ans = (vec) {0, 1};
	for (int i = 1; i <= m; i++) {
		if (k < b[i].first) {
			if (k == b[i - 1].first) {
				cout << ans.a[1] << endl;
				return 0;
			}
			ans = ans * make(b[i - 1].second, query(b[i - 1].first + 1)) * query(b[i - 1].first + 2, k);
			cout << ans.a[1] << endl;
			return 0;
		}
		if (b[i].first == b[i - 1].first + 1) ans = ans * make(b[i - 1].second, b[i].second);
		else ans = ans * make(b[i - 1].second, query(b[i - 1].first + 1))
				* query(b[i - 1].first + 2, b[i].first - 1)
				* make(query(b[i].first - 1), b[i].second);
	}
	return 0;
}

Codeforces 575E Spectator Riots

首先,圆是凸集,因此,若圆包含了所有点集中的点,它也一定包含了整个点集凸包。

因此,求出所有可能出现人的位置的凸包,我们只需要在凸包上考虑问题。

首先我们给出结论:答案一定是凸包上连续的三个点组成的三角形的外接圆。

这是因为,一个不满足上述条件的圆可以通过调整变得更大。

并且,如果一个圆没有包含整个凸包,则同样可以通过调整将其变大。

因此,只需要找到凸包上相邻的三个点外接圆半径的最大值即可,关于外接圆半径的计算可以考虑使用正弦定理。

时间复杂度 O ( N L o g N ) O(NLogN)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 8e5 + 5;
const int INF  = 1e5;
typedef long long ll;
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;
}
struct point {int x, y; };
point operator + (point a, point b) {return (point) {a.x + b.x, a.y + b.y}; }
point operator - (point a, point b) {return (point) {a.x - b.x, a.y - b.y}; }
point operator * (point a, int b) {return (point) {a.x * b, a.y * b}; }
long long operator * (point a, point b) {return 1ll * a.x * b.y - 1ll * a.y * b.x; }
long long dist(point a) {return 1ll * a.x * a.x + 1ll * a.y * a.y; }
bool operator < (point a, point b) {
	if (a.y == b.y) return a.x < b.x;
	else return a.y < b.y;
}
int n, m;
point a[MAXN];
bool cmp(point x, point y) {
	if ((x - a[1]) * (y - a[1]) == 0) return dist(x - a[1]) < dist(y - a[1]);
	else return (x - a[1]) * (y - a[1]) > 0;
}
int main() {
	read(n);
	for (int i = 1; i <= n; i++) {
		point pos; int v;
		read(pos.x), read(pos.y), read(v);
		if (pos.x + v <= INF) a[++m] = (point) {pos.x + v, pos.y};
		else {
			a[++m] = (point) {INF, min(pos.y + v - (INF - pos.x), INF)};
			a[++m] = (point) {INF, max(pos.y + (INF - pos.x) - v, 0)};
		}
		if (pos.y + v <= INF) a[++m] = (point) {pos.x, pos.y + v};
		else {
			a[++m] = (point) {min(pos.x + v - (INF - pos.y), INF), INF};
			a[++m] = (point) {max(pos.x + (INF - pos.y) - v, 0), INF};
		}
		if (pos.x >= v) a[++m] = (point) {pos.x - v, pos.y};
		else {
			a[++m] = (point) {0, min(pos.y + v - pos.x, INF)};
			a[++m] = (point) {0, max(pos.y - v + pos.x, 0)};
		}
		if (pos.y >= v) a[++m] = (point) {pos.x, pos.y - v};
		else {
			a[++m] = (point) {min(pos.x + v - pos.y, INF), 0};
			a[++m] = (point) {max(pos.x - v + pos.y, 0), 0};
		}
	}
	for (int i = 1; i <= m; i++)
		if (a[i] < a[1]) swap(a[i], a[1]);
	sort(a + 2, a + m + 1, cmp), a[m + 1] = a[1];
	int top = 1;
	for (int i = 2; i <= m + 1; i++) {
		while (top >= 2 && (a[i] - a[top - 1]) * (a[top] - a[top - 1]) >= 0) top--;
		a[++top] = a[i];
	}
	point p, q, r; double ans = 0; a[top + 1] = a[2];
	for (int i = 2; i <= top; i++) {
		point x = a[i + 1] - a[i], y = a[i - 1] - a[i], z = a[i + 1] - a[i - 1];
		double cur = sqrt(dist(x)) * sqrt(dist(y)) * sqrt(dist(z)) / (x * y);
		if (cur > ans) {
			ans = cur;
			p = a[i - 1], q = a[i], r = a[i + 1];
		}
	}
	printf("%d %d\n%d %d\n%d %d\n", p.x, p.y, q.x, q.y, r.x, r.y);
	return 0;
}

Codeforces 575I Robots Protection

离线操作,考虑分治,即计算 [ l , m i d ] [l,mid] 中的修改对 [ m i d + 1 , r ] [mid+1,r] 中的询问的影响。

对于 1 1 类三角形,其划分的区域应当为
[ x [ x 0 , x 0 + l e n ] , y y 0 ] [ x [ x 0 , x 0 + l e n ] , x + y > x 0 + y 0 + l e n ] = 1 [x\in[x_0,x_0+len],y\geq y_0]-[x\in[x_0,x_0+len],x+y>x_0+ y_0+len]=1 的区域。

因此,可以看做两个分立的二维数点问题,扫描线 + + 树状数组即可。

时间复杂度 O ( Q L o g 2 N ) O(QLog^2N)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
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;
}
struct BinaryIndexTree {
	int n, a[MAXN];
	void init(int x) {
		n = x;
		memset(a, 0, sizeof(a));
	}
	void modify(int x, int d) {
		for (int i = x; i <= n; i += i & -i)
			a[i] += d;
	}
	int query(int x) {
		int ans = 0;
		for (int i = x; i >= 1; i -= i & -i)
			ans += a[i];
		return ans;
	}
} BIT[3];
int n, q, ans[MAXN];
struct info {int x, y, val, type; } a[MAXN];
struct event {int type, x, d, timer; } b[MAXN * 5];
bool cmp(event x, event y) {
	if (x.timer == y.timer) return x.type < y.type;
	else return x.timer < y.timer;
}
void add(int &tot, int l, int r, int type, int x, int d) {
	b[++tot] = (event) {-type, x, d, l};
	b[++tot] = (event) {-type, x, -d, r + 1};
}
void solve(int l, int r) {
	if (l == r) return;
	int mid = (l + r) / 2, tot = 0;
	solve(l, mid), solve(mid + 1, r);
	for (int i = l; i <= mid; i++)
		if (a[i].type) {
			int x = a[i].x, y = a[i].y, len = a[i].val;
			if (a[i].type == 1) {
				add(tot, x, x + len, 0, y, 1);
				add(tot, x, x + len, 1, x + y + len + 1, -1);
			}
			if (a[i].type == 2) {
				add(tot, x, x + len, 2, y - x + n - len, 1);
				add(tot, x, x + len, 0, y + 1, -1);
			}
			if (a[i].type == 3) {
				add(tot, x - len, x, 0, y, 1);
				add(tot, x - len, x, 2, y - x + n + len + 1, -1);
			}
			if (a[i].type == 4) {
				add(tot, x - len, x, 1, x + y - len, 1);
				add(tot, x - len, x, 0, y + 1, -1);
			}
		}
	for (int i = mid + 1; i <= r; i++)
		if (a[i].type == 0) b[++tot] = (event) {a[i].val, a[i].y, 0, a[i].x};
	sort(b + 1, b + tot + 1, cmp);
	for (int i = 1; i <= tot; i++) {
		if (b[i].type <= 0) BIT[-b[i].type].modify(b[i].x, b[i].d);
		else {
			int home = b[i].type, x = b[i].timer, y = b[i].x;
			ans[home] += BIT[0].query(y);
			ans[home] += BIT[1].query(x + y);
			ans[home] += BIT[2].query(y - x + n);
		}
	}
}
int main() {
	read(n), read(q);
	BIT[0].init(n * 2);
	BIT[1].init(n * 2);
	BIT[2].init(n * 2);
	for (int i = 1; i <= q; i++) {
		int opt; read(opt);
		if (opt == 1) {
			read(a[i].type), read(a[i].x);
			read(a[i].y), read(a[i].val);
		} else {
			read(a[i].x), read(a[i].y);
			a[i].type = 0, a[i].val = i;
		}
	}
	solve(1, q);
	for (int i = 1; i <= q; i++)
		if (a[i].type == 0) printf("%d\n", ans[i]);
	return 0;
}
发布了813 篇原创文章 · 获赞 93 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/qq_39972971/article/details/103709200