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

Codeforces 504E Misha and LCP on Tree

在树上进行字符串哈希即可。

本题的第 11 个测试点卡了利用自然溢出的字符串哈希。

时间复杂度 O(NLogN+MLogN)O(NLogN+MLogN)O(NLogN+MLogN)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
const int P = 1e9 + 7;
const int Q = 998244353;
const int MAXLOG = 20;
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("");
}
struct ull {int a, b; };
int n, m, depth[MAXN], father[MAXN][MAXLOG];
ull bit[MAXLOG], val[MAXN][MAXLOG][2];
char s[MAXN]; vector <int> a[MAXN];
ull operator + (ull a, ull b) {return (ull) {(a.a + b.a) % P, (a.b + b.b) % Q}; }
ull operator * (ull a, ull b) {return (ull) {(1ll * a.a * b.a) % P, (1ll * a.b * b.b) % Q}; }
bool operator == (ull a, ull b) {return a.a == b.a && a.b == b.b; }
void dfs(int pos, int fa) {
	depth[pos] = depth[fa] + 1;
	father[pos][0] = fa;
	val[pos][0][0] = (ull) {s[pos], s[pos]};
	val[pos][0][1] = (ull) {s[pos], s[pos]};
	for (int i = 1; i < MAXLOG; i++) {
		father[pos][i] = father[father[pos][i - 1]][i - 1];
		val[pos][i][0] = val[pos][i - 1][0] + bit[i - 1] * val[father[pos][i - 1]][i - 1][0];
		val[pos][i][1] = bit[i - 1] * val[pos][i - 1][1] + val[father[pos][i - 1]][i - 1][1];
	}
	for (auto x : a[pos])
		if (x != fa) dfs(x, pos);
}
int lca(int x, int y) {
	if (depth[x] < depth[y]) swap(x, y);
	for (int i = MAXLOG - 1; i >= 0; i--)
		if (depth[father[x][i]] >= depth[y]) x = father[x][i];
	if (x == y) return x;
	for (int i = MAXLOG - 1; i >= 0; i--)
		if (father[x][i] != father[y][i]) {
			x = father[x][i];
			y = father[y][i];
		}
	return father[x][0];
}
int up(int x, int y) {
	for (int i = 0; i < MAXLOG; i++)
		if (y & (1 << i)) x = father[x][i];
	return x;
}
int conlcp(int a, int b, int c, int d) {
	int len = depth[a] - depth[b] + 1, ans = 0;
	assert(depth[d] - depth[c] + 1 == len);
	static int x[MAXN]; x[0] = d;
	for (int i = 0, t = 1; i < MAXLOG; i++, t <<= 1) {
		x[i + 1] = x[i];
		if (len & t) x[i + 1] = father[x[i]][i];
	}
	for (int i = MAXLOG - 1, t = 1 << i; i >= 0; i--, t >>= 1) {
		if (len & t) {
			if (val[a][i][0] == val[x[i]][i][1]) ans += t, a = father[a][i];
			else {
				int tmpx = a, tmpy = x[i];
				for (int j = i - 1; j >= 0; j--)
					if (val[tmpx][j][0] == val[father[tmpy][j]][j][1]) ans += 1 << j, tmpx = father[tmpx][j];
					else tmpy = father[tmpy][j];
				return ans;
			}
		}
	}
	return len;
}
int samelcp(int a, int b, int c, int d) {
	int len = depth[b] - depth[a] + 1, ans = 0;
	assert(depth[d] - depth[c] + 1 == len);
	static int x[MAXN], y[MAXN]; x[0] = b, y[0] = d;
	for (int i = 0, t = 1; i < MAXLOG; i++, t <<= 1) {
		x[i + 1] = x[i], y[i + 1] = y[i];
		if (len & t) {
			x[i + 1] = father[x[i]][i];
			y[i + 1] = father[y[i]][i];
		}
	}
	for (int i = MAXLOG - 1, t = 1 << i; i >= 0; i--, t >>= 1) {
		if (len & t) {
			if (val[x[i]][i][0] == val[y[i]][i][0]) ans += t;
			else {
				int tmpx = x[i], tmpy = y[i];
				for (int j = i - 1; j >= 0; j--)
					if (val[father[tmpx][j]][j][0] == val[father[tmpy][j]][j][0]) ans += 1 << j;
					else tmpx = father[tmpx][j], tmpy = father[tmpy][j];
				return ans;
			}
		}
	}
	return len;
}
int main() {
	read(n), scanf("\n%s", s + 1);
	for (int i = 1; i <= n - 1; i++) {
		int x, y; read(x), read(y);
		a[x].push_back(y);
		a[y].push_back(x);
	}
	bit[0] = (ull) {256, 256};
	for (int i = 1; i < MAXLOG; i++)
		bit[i] = bit[i - 1] * bit[i - 1];
	dfs(1, 0);
	read(m);
	for (int x = 1; x <= m; x++) {
		int xs, xt, ys, yt;
		read(xs), read(xt);
		read(ys), read(yt);
		int xl = lca(xs, xt), yl = lca(ys, yt), ans = 0;
		for (int i = MAXLOG - 1; i >= 0; i--)
			if (depth[father[xs][i]] >= depth[xl] && depth[father[ys][i]] >= depth[yl] && val[xs][i][0] == val[ys][i][0]) {
				ans += 1 << i;
				xs = father[xs][i];
				ys = father[ys][i];
			}
		if (xl != xs && yl != ys) {
			printf("%d\n", ans);
			continue;
		}
		if (yl != ys) swap(xl, yl), swap(xs, ys), swap(xt, yt);
		int xlen = depth[xs] + depth[xt] - 2 * depth[xl] + 1;
		int ylen = depth[ys] + depth[yt] - 2 * depth[yl] + 1;
		if (ylen >= xlen) yt = up(yt, ylen - xlen), ylen = xlen;
		else {
			if (depth[xt] - depth[xl] >= xlen - ylen) xt = up(xt, xlen - ylen);
			else xl = xt = up(xs, ylen - 1);
			xlen = ylen; 
		}
		if (xs != xl) {
			int tmp = conlcp(xs, up(xs, depth[xs] - depth[xl] - 1), ys, up(yt, ylen - (depth[xs] - depth[xl])));
			ans += tmp;
			if (tmp != depth[xs] - depth[xl]) {
				printf("%d\n", ans);
				continue;
			}
		}
		ans += samelcp(xl, xt, up(yt, ylen - (depth[xs] - depth[xl]) - 1 ), yt);
		printf("%d\n", ans);
	}
	return 0;
}

Codeforces 506C Mr. Kitayuta vs. Bamboos

首先二分答案 AnsAnsAns ,此后,我们需要判断是否能将最高的竹子砍到 AnsAnsAns 以下。

则每棵竹子 iii 存在 reqireq_ireqi 表示至少要砍掉多少长度,显然,至少要砍 ⌈reqip⌉\lceil\frac{req_i}{p}\rceilpreqi 次。

考虑一个合法的方案,存在竹子 iii 被砍了超过 ⌈reqip⌉\lceil\frac{req_i}{p}\rceilpreqi 次,我们可以选择不砍最开始的几次,只砍最后的 ⌈reqip⌉\lceil\frac{req_i}{p}\rceilpreqi 次,同样可以完成任务,因此,可以认为一棵竹子一定会被砍 cnti=⌈reqip⌉cnt_i=\lceil\frac{req_i}{p}\rceilcnti=preqi 次。

∑cnti>MK\sum cnt_i>MKcnti>MK ,则显然无解,否则,分别考虑每一刀,在砍最后一刀时需要保证竹子已经长出了 reqireq_ireqi ,最后第二刀时需要保证竹子已经长出了 reqi−preq_i-preqip ,以此类推。并且,不难发现这些条件是充分必要的,因此,每一刀都需要独立地在某个时刻后被砍,可以用后缀和判断是否合法。

时间复杂度 O((N+MK)LogV)O((N+MK)LogV)O((N+MK)LogV)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 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;
}
int n, m, k, p;
ll a[MAXN], h[MAXN], req[MAXN], cnt[MAXN];
bool check(ll x) {
	ll tot = 0;
	for (int i = 1; i <= n; i++) {
		ll final = a[i] * m + h[i];
		if (final <= x) req[i] = cnt[i] = 0;
		else {
			req[i] = final - x;
			cnt[i] = req[i] / p + (req[i] % p != 0);
		}
		tot += cnt[i];
	}
	if (tot > m * k) return false;
	static int suf[MAXN];
	memset(suf, 0, sizeof(suf));
	for (int i = 1; i <= n; i++) {
		ll tmp = req[i];
		while (tmp > 0) {
			if (tmp <= h[i]) suf[1]++;
			else {
				ll tnp = 1 + (tmp - h[i]) / a[i] + ((tmp - h[i]) % a[i] != 0);
				if (tnp > m) return false;
				suf[tnp]++;
			}
			tmp -= p;
		}
	}
	for (int i = m; i >= 1; i--) {
		suf[i] += suf[i + 1];
		if (suf[i] > (m - i + 1) * k) return false;
	}
	return true;
}
int main() {
	read(n), read(m), read(k), read(p);
	for (int i = 1; i <= n; i++)
		read(h[i]), read(a[i]);
	ll l = 0, r = 1e15;
	while (l < r) {
		ll mid = (l + r) / 2;
		if (check(mid)) r = mid;
		else l = mid + 1;
	}
	cout << r << endl;
	return 0;
}

Codeforces 506E Mr. Kitayuta’s Gift

首先有一个简单的 O(∣S∣3+N×∣S∣2)O(|S|^3+N\times|S|^2)O(S3+N×S2) 的 dp 做法,即从结果串的两边向中间 dp 。

这个方法同样可以对于所有 M≤NM≤NMN 算出所有答案。

由数据范围,本题的正解很可能是矩阵乘法优化 dp ,不妨用上述 dp 计算较小范围的答案,然后尝试用 Berlekamp-Massey 算法解出答案的递推式。

经尝试,答案存在一个不超过 3∣S∣+53|S|+53S+5 阶的线性递推式。

用 Cayley-Hamilton 定理优化递推即可。

时间复杂度 O(∣S∣3+∣S∣2LogN)O(|S|^3+|S|^2LogN)O(S3+S2LogN)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 205;
const int P = 1e4 + 7;
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("");
}
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
namespace LinearSequence {
	const int MAXN = 1205;
	int cnt, delta[MAXN], fail[MAXN];
	int k, h[MAXN], r[MAXN];
	vector <int> a[MAXN];
	int power(int x, int y) {
		if (y == 0) return 1;
		int tmp = power(x, y / 2);
		if (y % 2 == 0) return tmp * tmp % P;
		else return tmp * tmp % P * x % P;
	}
	void times(int *a, int *b, int *res) {
		static int tmp[MAXN];
		memset(tmp, 0, sizeof(tmp));
		for (int i = 0; i <= k - 1; i++)
		for (int j = 0; j <= k - 1; j++)
			update(tmp[i + j], a[i] * b[j] % P);
		for (int i = 2 * k - 1; i >= k; i--) {
			int val = tmp[i];
			for (int j = 1; j <= k; j++)
				update(tmp[i - j], val * r[j] % P);
		}
		for (int i = 0; i <= k - 1; i++)
			res[i] = tmp[i];
	}
	int getans(int n) {
		if (n <= k) return h[n];
		static int tmp[MAXN], res[MAXN];
		memset(tmp, 0, sizeof(tmp));
		memset(res, 0, sizeof(res));
		n -= k, res[0] = 1, tmp[1] = 1;
		for (int bit = 1; n != 0; bit <<= 1) {
			if (bit & n) {
				n ^= bit;
				times(res, tmp, res);
			}
			times(tmp, tmp, tmp);
		}
		int ans = 0;
		for (int i = 0; i <= k - 1; i++)
			update(ans, res[i] * h[i + k] % P);
		return ans;
	}
	void work(int given, int *val, int n) {
		a[cnt = 0].clear();
		for (int i = 1; i <= given; i++) {
			delta[i] = val[i];
			for (unsigned j = 0; j < a[cnt].size(); j++)
				update(delta[i], P - a[cnt][j] * val[i - j - 1] % P);
			if (delta[i] == 0) continue;
			fail[cnt] = i;
			if (cnt == 0) {
				a[++cnt].clear();
				a[cnt].resize(i);
				continue;
			}
			int mul = delta[i] * power(delta[fail[cnt - 1]], P - 2) % P;
			a[cnt + 1].clear(), a[cnt + 1].resize(i - fail[cnt - 1] - 1);
			a[cnt + 1].push_back(mul);
			for (unsigned i = 0; i < a[cnt - 1].size(); i++)
				a[cnt + 1].push_back((P - a[cnt - 1][i]) * mul % P);
			a[cnt + 1].resize(max(a[cnt + 1].size(), a[cnt].size()));
			for (unsigned i = 0; i < a[cnt].size(); i++)
				update(a[cnt + 1][i], a[cnt][i]);
			cnt++;
		}
		k = a[cnt].size();
		for (int i = 1; i <= 2 * k; i++)
			h[i] = val[i];
		for (int i = 1; i <= k; i++)
			r[i] = a[cnt][i - 1];
		writeln(getans(n));
	}
}
char s[MAXN];
int len, n, ans[MAXN * 8], done[MAXN * 4];
int dp[MAXN * 4][MAXN][MAXN];
int main() {
	scanf("%s", s + 1);
	len = strlen(s + 1), read(n);
	dp[0][1][len] = 1;
	for (int k = 1; k <= len * 4 + 5; k++) {
		done[k] = done[k - 1] * 26 % P;
		for (int i = 1; i <= len; i++)
		for (int j = i; j <= len; j++) {
			if (s[i] == s[j]) {
				if (i + 1 <= j - 1) update(dp[k][i + 1][j - 1], dp[k - 1][i][j]);
				else update(done[k], dp[k - 1][i][j]);
				update(dp[k][i][j], dp[k - 1][i][j] * 25 % P);
			} else {
				if (i == j) update(done[k], dp[k - 1][i][j] * 2 % P);
				else {
					update(dp[k][i + 1][j], dp[k - 1][i][j]);
					update(dp[k][i][j - 1], dp[k - 1][i][j]);
				}
				update(dp[k][i][j], dp[k - 1][i][j] * 24 % P);
			}
		}
	}
	for (int i = 1; i <= len * 8 + 5; i++) {
		if (i & 1) {
			ans[i] = done[i / 2] * 26 % P;
			for (int j = 1; j <= len; j++)
				update(ans[i], dp[i / 2][j][j]);
		} else ans[i] = done[i / 2];
	}
	LinearSequence :: work(len * 7 + 5, ans + len, n);
	return 0;
}

Codeforces 512D Fox And Travelling

首先,显然各个联通块是相互独立的,最后可以用指数型生成函数卷积计算答案。

对于一个联通块,考虑计算数组 dpidp_idpi 表示在连通块中删去 iii 个节点的方案数。

考虑枚举一个不会被删去,或是最后被删去的根节点,累加各个根节点的贡献,最后将 dpidp_{i}dpi 除去 s−is-isi 即可,其中 sss 表示连通块大小。

确定根节点后,被删去的叶子一定是若干个无环的子树,它们分别的删除序列数即为各自的外向拓扑排序数,并且同样可以用指数型生成函数卷积计算总方案数,因此,只需要在搜索树上动态规划即可。

时间复杂度 O(N3+N×M)O(N^3+N\times M)O(N3+N×M)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 105;
const int P = 1e9 + 9;
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;
}
vector <int> a[MAXN];
bool vis[MAXN], c[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;
}
vector <int> p;
int v[MAXN], e[MAXN], f[MAXN], dp[MAXN][MAXN];
int n, m, cnt, ans[MAXN], fac[MAXN], inv[MAXN];
void dfs(int pos) {
	vis[pos] = true, p.push_back(pos);
	for (auto x : a[pos]) if (!vis[x]) dfs(x); 
}
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
void work(int pos) {
	memset(dp[pos], 0, sizeof(dp[pos]));
	dp[pos][0] = f[pos] = 1, c[pos] = true;
	v[pos] = 1, e[pos] = a[pos].size();
	for (auto x : a[pos]) {
		if (!c[x]) {
			work(x);
			f[pos] = 1ll * f[pos] * f[x] % P;
			static int tmp[MAXN];
			memset(tmp, 0, sizeof(tmp));
			for (int i = 0; i <= v[pos]; i++)
			for (int j = 0; j <= v[x]; j++)
				update(tmp[i + j], 1ll * dp[pos][i] * dp[x][j] % P);
			memcpy(dp[pos], tmp, sizeof(tmp));
			v[pos] += v[x];
			e[pos] += e[x];
		}
	}
	f[pos] = 1ll * f[pos] * inv[v[pos]] % P * fac[v[pos] - 1] % P;
	if (e[pos] <= 2 * v[pos] - 1) update(dp[pos][v[pos]], f[pos]);
}
int main() {
	read(n), read(m);
	fac[0] = inv[0] = 1;
	for (int i = 1; i <= n; i++) {
		fac[i] = 1ll * fac[i - 1] * i % P;
		inv[i] = power(fac[i], P - 2);
	}
	for (int i = 1; i <= m; i++) {
		int x, y; read(x), read(y);
		a[x].push_back(y);
		a[y].push_back(x);
	}
	ans[cnt = 0] = 1;
	for (int i = 1; i <= n; i++) {
		if (vis[i]) continue;
		p.clear(), dfs(i);
		static int res[MAXN]; int s = p.size();
		memset(res, 0, sizeof(res));
		for (auto x : p) {
			memset(c, 0, sizeof(c)), work(x);
			for (int j = 0; j <= s; j++)
				update(res[j], dp[x][j]);
		}
		static int tmp[MAXN];
		memset(tmp, 0, sizeof(tmp));
		for (int j = 0; j <= s; j++) {
			int lft = s - j;
			if (lft != 0) res[j] = 1ll * res[j] * inv[lft] % P * fac[lft - 1] % P;
			for (int k = 0; k <= cnt; k++)
				update(tmp[j + k], 1ll * res[j] * ans[k] % P);
		}
		cnt += s;
		memcpy(ans, tmp, sizeof(tmp));
	}
	for (int i = 0; i <= n; i++)
		printf("%lld\n", 1ll * ans[i] * fac[i] % P);
	return 0;
}

Codeforces 516D Drazil and Morning Exercise

求出树的直径 (x,y)(x,y)(x,y) ,则树可以分为两部分,到每一部分的最远点是 x,yx,yx,y 之一。

若选定子集横跨两个部分,则所选子集一定是对应距离最短的若干个点,可以简单计算。

若选定子集完全在某个部分内,则考虑枚举所选的最小的元素 xxx ,其余元素应当是 xxx 子树内最小的若干元素,可以通过启发式合并堆计算答案。

时间复杂度 O(NLogN+QNLog2N)O(NLogN+QNLog^2N)O(NLogN+QNLog2N) ,可以用并查集优化至 O(NLogN+QNα(N))O(NLogN+QN\alpha(N))O(NLogN+QNα(N))

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const int MAXLOG = 20;
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, q, rt[MAXN], depth[MAXN], father[MAXN][MAXLOG];
ll l, path[MAXN], dist[MAXN], res[MAXN];
vector <pair <int, int>> a[MAXN];
priority_queue <ll> Heap[MAXN];
void getans(int pos, int fa) {
	while (!Heap[pos].empty()) Heap[pos].pop();
	Heap[pos].push(res[pos]);
	for (auto x : a[pos])
		if (x.first != fa) {
			getans(x.first, pos);
			while (Heap[x.first].size() > Heap[pos].size()) swap(Heap[x.first], Heap[pos]);
			while (!Heap[x.first].empty()) {
				Heap[pos].push(Heap[x.first].top());
				Heap[x.first].pop();
			}
		}
	while (Heap[pos].top() - res[pos] > l) Heap[pos].pop();
	rt[pos] = Heap[pos].size();
}
void work(int pos, int fa) {
	depth[pos] = depth[fa] + 1;
	father[pos][0] = fa;
	for (int i = 1; i < MAXLOG; i++)
		father[pos][i] = father[father[pos][i - 1]][i - 1];
	for (unsigned i = 0; i < a[pos].size(); i++)
		if (a[pos][i].first != fa) {
			path[a[pos][i].first] = path[pos] + a[pos][i].second;
			work(a[pos][i].first, pos);
		}
}
int lca(int x, int y) {
	if (depth[x] < depth[y]) swap(x, y);
	for (int i = MAXLOG - 1; i >= 0; i--)
		if (depth[father[x][i]] >= depth[y]) x = father[x][i];
	if (x == y) return x;
	for (int i = MAXLOG - 1; i >= 0; i--)
		if (father[x][i] != father[y][i]) {
			x = father[x][i];
			y = father[y][i];
		}
	return father[x][0];
}
void dfs(int pos, int fa) {
	for (auto x : a[pos])
		if (x.first != fa) {
			dist[x.first] = dist[pos] + x.second;
			dfs(x.first, pos);
		}
}
int main() {
	read(n);
	for (int i = 1; i <= n - 1; i++) {
		int x, y, z;
		read(x), read(y), read(z);
		a[x].emplace_back(y, z);
		a[y].emplace_back(x, z);
	}
	work(1, 0);
	int x = 0, y = 0;
	dist[1] = 0, dfs(1, 0);
	for (int i = 1; i <= n; i++)
		if (dist[i] > dist[x]) x = i;
	dist[x] = 0, dfs(x, 0);
	for (int i = 1; i <= n; i++)
		if (dist[i] > dist[y]) y = i;
	ll Min = 1e18; int rx = 0, ry = 0;
	for (int i = 1; i <= n; i++) {
		ll lenx = path[i] + path[x] - 2 * path[lca(i, x)];
		ll leny = path[i] + path[y] - 2 * path[lca(i, y)];
		res[i] = max(lenx, leny);
		chkmin(Min, res[i]);
		if (lenx > leny) {
			if (rx == 0 || res[i] < res[rx]) rx = i;
		} else {
			if (ry == 0 || res[i] < res[ry]) ry = i;
		}
	}
	read(q);
	while (q--) {
		read(l);
		int ans = 0;
		for (int i = 1; i <= n; i++)
			if (res[i] - Min <= l) ans++;
		getans(rx, ry);
		getans(ry, rx);
		for (int i = 1; i <= n; i++)
			chkmax(ans, rt[i]);
		printf("%d\n", ans);
	}
	return 0;
}

Codeforces 516E Drazil and His Happy Friends

N,MN,MN,M 不互质,问题可以拆成 gcd(N,M)gcd(N,M)gcd(N,M) 个等价的问题分别处理,考虑 N,MN,MN,M 互质的情况。

b=g=0b=g=0b=g=0 ,显然答案为 −1-11

否则,考虑快乐的传播,若 iii 时刻交流的两人存在快乐的人,那么 i+N,i+Mi+N,i+Mi+N,i+M 时刻交流的人也会存在快乐的人,因此 iii 时刻的快乐能够传播到的范围为 i+xN+yM (x,y∈N)i+xN+yM\ (x,y\in\N)i+xN+yM (x,yN)

对于每个人,我们关心的是 i+xN+yMi+xN+yMi+xN+yMNNNMMM 的余数,以 111 号人群为例,第 xxx 个人最早被传播的时刻即为使得 i+yM≡x (mod N)i+yM\equiv x\ (mod\ N)i+yMx (mod N) 的最小的 i+yMi+yMi+yM

0,1,2,…,N−10,1,2,\dots,N-10,1,2,,N1 中的所有数按照 0,M mod N,2M mod N,…,(N−1)M mod N0,M\ mod\ N,2M\ mod\ N,\dots,(N-1)M\ mod\ N0,M mod N,2M mod N,,(N1)M mod N 的顺序形成了一个环,分别考虑每个关键点在环上的后继即可。

时间复杂度 O((b+g)×(LogN+LogM))O((b+g)\times (LogN+LogM))O((b+g)×(LogN+LogM))

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
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("");
}
vector <int> a[MAXN];
void exgcd(int a, int b, int &x, int &y) {
	if (b == 0) {
		x = 1;
		y = 0;
		return;
	}
	int q = a / b, r = a % b;
	exgcd(b, r, y, x);
	y -= q * x;
}
ll calc(int n, int m, vector <int> a) {
	int x = 0, inv = 0;
	exgcd(n, m, x, inv);
	inv = (inv % n + n) % n;
	static pair <int, int> s[MAXN]; int tot = 0;
	for (auto &x : a) s[++tot] = make_pair(1ll * x * inv % n, x);
	sort(s + 1, s + tot + 1);
	ll ans = 0;
	for (int i = 2; i <= tot; i++) {
		if (s[i].first == s[i - 1].first) chkmin(s[i].second, s[i - 1].second);
		if (s[i].first - s[i - 1].first >= 2) chkmax(ans, (s[i].first - s[i - 1].first - 1ll) * m + s[i - 1].second);
	}
	if (s[1].first + n - s[tot].first >= 2) chkmax(ans, (s[1].first + n - s[tot].first - 1ll) * m + s[tot].second);
	return ans;
}
ll getans(int x, int y, vector <int> a) {
	return max(calc(x, y, a), calc(y, x, a));
}
ll get(vector <int> a, int n) {
	if (a.size() == n) return 0;
	sort(a.begin(), a.end());
	while (a.size() != 0 && a.back() == n - 1) a.pop_back(), n--;
	return n - 1;
}
int main() {
	int n, m; read(n), read(m);
	int g = __gcd(n, m);
	if (g >= MAXN) {
		puts("-1");
		return 0;
	}
	int cnt; read(cnt);
	vector <int> tx, ty;
	while (cnt--) {
		int x; read(x);
		a[x % g].push_back(x / g);
		tx.push_back(x);
	}
	read(cnt);
	while (cnt--) {
		int x; read(x);
		a[x % g].push_back(x / g);
		ty.push_back(x);
	}
	ll ans = max(get(tx, n), get(ty, m));
	for (int i = 0; i <= g - 1; i++)
		if (a[i].empty()) {
			puts("-1");
			return 0;
		} else {
			sort(a[i].begin(), a[i].end());
			a[i].erase(unique(a[i].begin(), a[i].end()), a[i].end());
			if (a[i].size() < max(n, m) / g) chkmax(ans, 1ll * g * getans(n / g, m / g, a[i]) + i);
		}
	writeln(ans);
	return 0;
}

Codeforces 521D Shop

对于一个确定的操作集合,显然应该先操作 111 类操作,再操作 222 类操作,最后操作 333 类操作。

显然,对于同一个元素, 111 类操作最多进行一次,因此,计算其增量,可以将其视为一个 222 类操作。

并且,对于同一个元素的所有 222 类操作,被选中的一定是增量最大的若干个操作,因此,可以认为 222 类操作是按照增量从大到小的顺序进行的,从而 222 类操作同样可以视为对答案乘上一个常数。

由此,将所有操作对答案所乘的常数按大到小排序,即可求得方案。

时间复杂度 O(NLogN+K)O(NLogN+K)O(NLogN+K)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
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;
}
int k, n, m, tot;
int Maxone[MAXN]; ll v[MAXN];
vector <pair <int, int>> inc[MAXN];
pair <pair <ll, ll>, int> a[MAXN];
int opt[MAXN], who[MAXN], w[MAXN];
pair <ll, ll> prod(ll a, ll b) {
	pair <ll, ll> res = make_pair(a / P * b + b / P * a - (a / P) * (b / P) * P, (a % P) * (b % P));
	res.first += res.second / P;
	res.second %= P;
	return res;
}
bool cmp(pair <pair <ll, ll>, int> a, pair <pair <ll, ll>, int> b) {
	return prod(a.first.first, b.first.second) > prod(a.first.second, b.first.first);
}
int main() {
	read(k), read(n), read(m);
	for (int i = 1; i <= k; i++)
		read(v[i]);
	for (int i = 1; i <= n; i++) {
		read(opt[i]), read(who[i]), read(w[i]);
		if (opt[i] == 1 && w[i] > w[Maxone[who[i]]]) Maxone[who[i]] = i;
		if (opt[i] == 2) inc[who[i]].emplace_back(w[i], i);
		if (opt[i] == 3) a[++tot] = make_pair(make_pair(w[i], 1), i);
	}
	for (int i = 1; i <= k; i++)
		if (w[Maxone[i]] > v[i]) inc[i].emplace_back(w[Maxone[i]] - v[i], Maxone[i]);
	for (int i = 1; i <= k; i++) {
		sort(inc[i].begin(), inc[i].end());
		reverse(inc[i].begin(), inc[i].end());
		for (auto x : inc[i]) {
			a[++tot] = make_pair(make_pair(v[i] + x.first, v[i]), x.second);
			v[i] += x.first;
		}
	}
	sort(a + 1, a + tot + 1, cmp);
	vector <int> ans;
	for (int i = 1; i <= min(m, tot); i++)
		ans.push_back(a[i].second);
	cout << ans.size() << endl;
	sort(ans.begin(), ans.end(), [&] (int x, int y) {return opt[x] < opt[y]; });
	for (auto x : ans) printf("%d ", x);
	return 0;
}

Codeforces 521E Cycling City

我们需要找到一个环,以及与该环仅交于某两个点的一条路径。

因此,可以分别考虑每个点双联通分量,若某点双不是环,且不是路径,则可以构造出一个方案。

在点双内 DFS 找到一个环,删去环边,再 DFS 找到一条路径即可。

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

#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;
}
bool instack[MAXN], ine[MAXN], key[MAXN], thr[MAXN], vis[MAXN];
vector <pair <int, int>> a[MAXN];
int n, m, timer, dfn[MAXN], low[MAXN];
int top, Stack[MAXN], se[MAXN], cnte[MAXN];
void getans(int pos, int fa, int dest, vector <int> &cur) {
	if (pos == dest) {
		cout << cur.size();
		for (auto x : cur) printf(" %d", x);
		printf("\n");
		return;
	}
	for (auto x : a[pos])
		if (x.first != fa && ine[x.second]) {
			cur.push_back(x.first);
			getans(x.first, pos, dest, cur);
			cur.pop_back();
		}
}
void findpath(int pos, vector <int> &cur) {
	if (instack[pos]) {
		cout << cur.size();
		assert(pos != cur[0]);
		for (auto x : cur) printf(" %d", x);
		printf("\n");
		vector <int> tmp = {cur[0]};
		getans(cur[0], 0, cur.back(), tmp);
		exit(0);
	}
	vis[pos] = true;
	for (auto x : a[pos])
		if (key[x.first] && !vis[x.first] && !ine[x.second] && !thr[x.second]) {
			cur.push_back(x.first);
			thr[x.second] = true;
			findpath(x.first, cur);
			cur.pop_back();
		}
}
void dfs(int pos) {
	instack[pos] = true;
	Stack[++top] = pos;
	for (auto x : a[pos])
		if (!ine[x.second]) {
			ine[x.second] = true;
			se[top] = x.second;
			if (instack[x.first]) {
				for (int i = 1; i <= top; i++) {
					if (Stack[i] == x.first) break;
					instack[Stack[i]] = false;
					ine[se[i]] = false;
				}
				for (int i = 1; i <= n; i++) {
					if (!instack[i]) continue;
					for (auto y : a[i])
						if (key[y.first] && !ine[y.second]) {
							thr[y.second] = true;
							vector <int> cur = {i, y.first};
							vis[i] = true;
							findpath(y.first, cur);
							assert(false);
						}
				}
				assert(false);
				exit(0);
			} else if (key[x.first]) dfs(x.first);
			ine[x.second] = false;
		}
	top--, instack[pos] = false;
}
void solve(vector <int> s) {
	for (auto x : s) key[x] = true;
	memset(ine, false, sizeof(ine));
	memset(instack, false, sizeof(instack));
	top = 0, dfs(s[0]);
}
void tarjan(int pos) {
	instack[pos] = true;
	dfn[pos] = low[pos] = ++timer;
	Stack[++top] = pos, cnte[top] = 0;
	int upe = 0, tmp = top;
	for (auto x : a[pos]) {
		if (dfn[x.first] == 0) {
			tarjan(x.first);
			chkmin(low[pos], low[x.first]);
			if (dfn[pos] == low[x.first]) {
				vector <int> s = {pos}; int e = 0;
				int tmp = Stack[top];
				s.push_back(tmp), e += cnte[top--];
				while (tmp != x.first) {
					tmp = Stack[top];
					s.push_back(tmp), e += cnte[top--];
				}
				if (e >= s.size() + 1) {
					puts("YES");
					solve(s);
					assert(false);
					exit(0);
				}
			}
		} else chkmin(low[pos], dfn[x.first]), upe += instack[x.first];
	}
	cnte[tmp] = upe;
	instack[pos] = false;
}
int main() {
	read(n), read(m);
	for (int i = 1; i <= m; i++) {
		int x, y; read(x), read(y);
		a[x].emplace_back(y, i);
		a[y].emplace_back(x, i);
	}
	for (int i = 1; i <= n; i++)
		if (dfn[i] == 0) tarjan(i);
	puts("NO");
	return 0;
}

Codeforces 526F Pudding Monsters

考虑分治,我们需要在 O(N)O(N)O(N) 的时间内计算横跨某一位置 midmidmid 的连续区间数量。

对于连续的区间,需要满足 Max−Min=r−lMax-Min=r-lMaxMin=rl

枚举最大,最小值的出现的位置在 midmidmid 的左侧还是右侧。

若最大,最小值出现在同侧,可以枚举对应的左(右)端点,并计算出右(左)端点,再判断是否合法。

若最大,最小值出现在不同侧,例如最大值在左,最小值在右,则有 l−Max=r−Minl-Max=r-MinlMax=rMin ,枚举 lll ,可以用哈希表维护合法的 rrr 的数量。并且,为了保证最大值在左,最小值在右, rrr 需要在一个特定的区间内,该区间的两个端点均是单调的,可以用双指针维护。

实现时需要使用 STSTST 表,时间复杂度 O(NLogN)O(NLogN)O(NLogN)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 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 rmq {
	const int MAXN = 3e5 + 5;
	const int MAXLOG = 19;
	int Max[MAXN][MAXLOG], Min[MAXN][MAXLOG], Log[MAXN];
	int queryMax(int l, int r) {
		int len = r - l + 1, tmp = Log[len];
		return max(Max[l][tmp], Max[r - (1 << tmp) + 1][tmp]);
	}
	int queryMin(int l, int r) {
		int len = r - l + 1, tmp = Log[len];
		return min(Min[l][tmp], Min[r - (1 << tmp) + 1][tmp]);
	}
	void init(int *a, int n) {
		for (int i = 1; i <= n; i++) {
			Min[i][0] = a[i];
			Max[i][0] = a[i];
			Log[i] = Log[i - 1];
			if ((1 << (Log[i] + 1)) <= i) Log[i]++;
		}
		for (int t = 1; t < MAXLOG; t++)
		for (int i = 1, j = (1 << (t - 1)) + 1; j <= n; i++, j++) {
			Max[i][t] = max(Max[i][t - 1], Max[j][t - 1]);
			Min[i][t] = min(Min[i][t - 1], Min[j][t - 1]);
		}
	}
}
int n, a[MAXN], cnt[MAXN * 2]; ll ans;
void solve(int l, int r) {
	if (l == r) return;
	int mid = (l + r) / 2;
	solve(l, mid);
	solve(mid + 1, r);
	// Both l
	for (int i = mid, Min = a[i], Max = a[i], pos = mid; i >= l; i--) {
		chkmin(Min, a[i]), chkmax(Max, a[i]);
		while (pos + 1 <= r && a[pos + 1] > Min && a[pos + 1] < Max) pos++;
		int j = i + Max - Min; ans += j > mid && j <= pos && rmq :: queryMin(i, j) == Min && rmq :: queryMax(i, j) == Max;
	}
	// Both r
	for (int i = mid + 1, Min = a[i], Max = a[i], pos = mid + 1; i <= r; i++) {
		chkmin(Min, a[i]), chkmax(Max, a[i]);
		while (pos - 1 >= l && a[pos - 1] > Min && a[pos - 1] < Max) pos--;
		int j = i - Max + Min; ans += j <= mid && j >= pos && rmq :: queryMin(j, i) == Min && rmq :: queryMax(j, i) == Max;
	}
	// l Max, r Min
	int pa = mid, pb = mid + 1;
	for (int i = mid, Min = a[i], Max = a[i]; i >= l; i--) {
		chkmin(Min, a[i]), chkmax(Max, a[i]);
		while (pa + 1 <= r && a[pa + 1] < Max) {pa++; cnt[pa + rmq :: queryMin(mid + 1, pa)]++; }
		while (pb <= r && rmq :: queryMin(i, pb) == Min) {cnt[pb + rmq :: queryMin(mid + 1, pb)]--; pb++;}
		if (pb <= pa) ans += cnt[Max + i];
	}
	if (pb <= pa) {
		for (int i = pb; i <= pa; i++)
			cnt[i + rmq :: queryMin(mid + 1, i)]--;
	} else {
		for (int i = pa + 1; i <= pb - 1; i++)
			cnt[i + rmq :: queryMin(mid + 1, i)]++;
	}
	// l Min, r Max
	pa = mid, pb = mid + 1;
	for (int i = mid, Min = a[i], Max = a[i]; i >= l; i--) {
		chkmin(Min, a[i]), chkmax(Max, a[i]);
		while (pa + 1 <= r && a[pa + 1] > Min) {pa++; cnt[n + pa - rmq :: queryMax(mid + 1, pa)]++; }
		while (pb <= r && rmq :: queryMax(i, pb) == Max) {cnt[n + pb - rmq :: queryMax(mid + 1, pb)]--; pb++;}
		if (pb <= pa) ans += cnt[n + i - Min];
	}
	if (pb <= pa) {
		for (int i = pb; i <= pa; i++)
			cnt[n + i - rmq :: queryMax(mid + 1, i)]--;
	} else {
		for (int i = pa + 1; i <= pb - 1; i++)
			cnt[n + i - rmq :: queryMax(mid + 1, i)]++;
	}
}
int main() {
	read(n);
	for (int i = 1; i <= n; i++) {
		int x, y; read(x), read(y);
		a[x] = y;
	}
	rmq :: init(a, n);
	ans = n, solve(1, n);
	cout << ans << endl;
	return 0;
}

Codeforces 526G Spiders Evil Plan

不妨无视要求链并连通的限制,因为最大的链并必然是连通的。

询问 (x,y)(x,y)(x,y) 可以如下转述:找到至多 2y2y2y 个叶子节点,使得这些点的虚树大小最大,且包含 xxx

不计复杂度地,考虑以 xxx 为根进行长链剖分,则应当选出最大的 2y2y2y 条长链,若这个方案使得 xxx 的度数为 111 ,则需要考虑一些边界情况。

注意到上面的做法选出的方案一定会包含直径的一端,可以枚举直径的某一端,以其为根处理询问。

对于询问 (x,y)(x,y)(x,y) ,若最大的 2y−12y-12y1 条长链包含 xxx ,则答案显然为这些长链的大小之和。否则,则需要删去其中最短的长链,或者最近的长链,具体可见下文代码中的 queryqueryquery 函数。

时间复杂度 O((N+Q)LogN)O((N+Q)LogN)O((N+Q)LogN)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
const int MAXLOG = 18;
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, q, depth[MAXN];
vector <pair <int, int>> a[MAXN];
struct Answer {
	int depth[MAXN], Max[MAXN], son[MAXN], leaf[MAXN];
	int father[MAXN][MAXLOG], Min[MAXN][MAXLOG];
	int inc[MAXN], sum[MAXN], vis[MAXN];
	pair <int, int> seg[MAXN]; int tot;
	void dfs(int pos, int fa) {
		father[pos][0] = fa;
		Max[pos] = depth[pos];
		leaf[pos] = pos;
		for (auto x : a[pos])
			if (x.first != fa) {
				depth[x.first] = depth[pos] + x.second;
				dfs(x.first, pos);
				if (Max[x.first] > Max[pos]) {
					leaf[pos] = leaf[x.first];
					Max[pos] = Max[x.first];
					son[pos] = x.first;
				}
			}
	}
	void efs(int pos, int fa) {
		if (pos != son[fa]) seg[++tot] = make_pair(Max[pos] - depth[fa], leaf[pos]);
		for (auto x : a[pos]) if (x.first != fa) efs(x.first, pos);
	}
	void init(int from) {
		dfs(from, 0);
		efs(from, 0);
		sort(seg + 1, seg + tot + 1);
		reverse(seg + 1, seg + tot + 1);
		for (int i = 1; i <= tot; i++) {
			int pos = seg[i].second;
			while (vis[pos] == 0) {
				vis[pos] = Min[pos][0] = i;
				pos = father[pos][0];
			}
			inc[i] = seg[i].first;
			sum[i] = inc[i] + sum[i - 1];
		}
		for (int p = 1; p < MAXLOG; p++)
		for (int i = 1; i <= n; i++) {
			father[i][p] = father[father[i][p - 1]][p - 1];
			Min[i][p] = min(Min[i][p - 1], Min[father[i][p - 1]][p - 1]);
		}
	}
	int query(int x, int y) {
		y = y * 2 - 1;
		if (y >= tot) return sum[tot];
		if (vis[x] <= y) return sum[y];
		int ans = sum[y], pos = x;
		for (int i = MAXLOG - 1; i >= 0; i--)
			if (Min[pos][i] > y) pos = father[pos][i];
		ans += Max[x] - depth[pos];
		ans -= min(inc[y], Max[pos] - depth[pos]);
		return ans;
	}
} s, t;
void getdepth(int pos, int fa) {
	for (auto x : a[pos])
		if (x.first != fa) {
			depth[x.first] = depth[pos] + x.second;
			getdepth(x.first, pos);
		}
}
int main() {
	read(n), read(q);
	for (int i = 1; i <= n - 1; i++) {
		int x, y, z;
		read(x), read(y), read(z);
		a[x].emplace_back(y, z);
		a[y].emplace_back(x, z);
	}
	getdepth(1, 0);
	int x = max_element(depth + 1, depth + n + 1) - depth;
	depth[x] = 0, getdepth(x, 0);
	int y = max_element(depth + 1, depth + n + 1) - depth;
	s.init(x), t.init(y);
	int lastans = 0;
	for (int i = 1; i <= q; i++) {
		int x, y; read(x), read(y);
		x = (x + lastans - 1) % n + 1;
		y = (y + lastans - 1) % n + 1;
		printf("%d\n", lastans = max(s.query(x, y), t.query(x, y)));
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值