【USACO】2019 January Contest, Platinum题解

**【T1】**Redistricting

【题目链接】

  • 点击打开链接

【题解链接】

  • 点击打开链接

【思路要点】

  • G G G 看做 + 1 +1 +1 H H H 看做 − 1 -1 1 ,记原数组前缀和为 s i s_i si
  • 则可以得到动态规划 d p i = ∑ j = 1 k d p i − j + [ s i − s i − j ≥ 0 ] dp_{i}=\sum_{j=1}^{k}dp_{i-j}+[s_i-s_{i-j}≥0] dpi=j=1kdpij+[sisij0]
  • 用线段树 + + + 堆维护最近的 k k k 个位置的 d p dp dp 值,在转移时枚举 s i − s i − j ≥ 0 s_i-s_{i-j}≥0 sisij0 是否成立,查询对应 s s s 的可行区间的区间最小值即可。
  • 时间复杂度 O ( N L o g N ) O(NLogN) O(NLogN)

【代码】

#include
using namespace std;
const int MAXN = 3e5 + 5;
const int INF = 2e9;
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("");
}
struct Heap {
	priority_queue <int, vector <int>, greater <int> > Main, Delt;
	void ins(int x) {Main.push(x); }
	void del(int x) {Delt.push(x); }
	int top() {
		while (!Main.empty() && !Delt.empty() && Main.top() == Delt.top()) {
			Main.pop();
			Delt.pop();
		}
		if (Main.empty()) return INF;
		else return Main.top();
	}
} Heap[MAXN * 2];
struct SegmentTree {
	struct Node {
		int lc, rc;
		int Min;
	} a[MAXN * 4];
	int n, root, size;
	void update(int root) {
		a[root].Min = min(a[a[root].lc].Min, a[a[root].rc].Min);
	}
	void build(int &root, int l, int r) {
		root = ++size;
		if (l == r) {
			a[root].Min = INF;
			return;
		}
		int mid = (l + r) / 2;
		build(a[root].lc, l, mid);
		build(a[root].rc, mid + 1, r);
		update(root);
	}
	void init(int x) {
		n = x, root = size = 0;
		build(root, 1, n);
	}
	void del(int root, int l, int r, int pos, int val) {
		if (l == r) {
			Heap[l].del(val);
			a[root].Min = Heap[l].top();
			return;
		}
		int mid = (l + r) / 2;
		if (mid >= pos) del(a[root].lc, l, mid, pos, val);
		else del(a[root].rc, mid + 1, r, pos, val);
		update(root);
	}
	void del(int pos, int val) {
		del(root, 1, n, pos, val);
	}
	void add(int root, int l, int r, int pos, int val) {
		if (l == r) {
			Heap[l].ins(val);
			a[root].Min = Heap[l].top();
			return;
		}
		int mid = (l + r) / 2;
		if (mid >= pos) add(a[root].lc, l, mid, pos, val);
		else add(a[root].rc, mid + 1, r, pos, val);
		update(root);
	}
	void add(int pos, int val) {
		add(root, 1, n, pos, val);
	}
	int query(int root, int l, int r, int ql, int qr) {
		if (l == ql && r == qr) return a[root].Min;
		int ans = INF, mid = (l + r) / 2;
		if (mid >= ql) chkmin(ans, query(a[root].lc, l, mid, ql, min(qr, mid)));
		if (mid + 1 <= qr) chkmin(ans, query(a[root].rc, mid + 1, r, max(mid + 1, ql), qr)); 
		return ans;
	}
	int query(int l, int r) {
		if (l > r) return INF;
		else return query(root, 1, n, l, r);
	}
} ST;
char s[MAXN];
int n, k, dp[MAXN], sum[MAXN];
int main() {
	freopen("redistricting.in", "r", stdin);
	freopen("redistricting.out", "w", stdout);
	read(n), read(k);
	scanf("\n%s", s + 1);
	sum[0] = n;
	for (int i = 1; i <= n; i++) {
		sum[i] = sum[i - 1];
		if (s[i] == 'G') sum[i]++;
		else sum[i]--;
	}
	ST.init(2 * n);
	ST.add(sum[0], dp[0]);
	for (int i = 1; i <= n; i++) {
		dp[i] = min(ST.query(1, sum[i]) + 1, ST.query(sum[i] + 1, 2 * n));
		ST.add(sum[i], dp[i]);
		if (i >= k) ST.del(sum[i - k], dp[i - k]);
	}
	writeln(dp[n]);
	return 0;
}

**【T2】**Exercise Route

【题目链接】

  • 点击打开链接

【题解链接】

  • 点击打开链接

【思路要点】

  • 问题等价于计算边相交的树上路径对数。
  • 注意到两条路径的交或为空,或为一条路径,考虑枚举该路径上深度最小的一条边 x x x ,记其上方一条边为 y y y ,包含 x x x 的路径数为 c n t x cnt_x cntx ,同时包含 x , y x,y x,y 的路径数为 c n t x , y cnt_{x,y} cntx,y 。则以 x x x 为相交路径上深度最小的一条边的路径对数为 ( c n t x 2 ) − ( c n t x , y 2 ) \binom{cnt_x}{2}-\binom{cnt_{x,y}}{2} (2cntx)(2cntx,y) c n t x , c n t x , y cnt_x,cnt_{x,y} cntx,cntx,y 可以通过树上差分得到。
  • 注意到一条路径可能存在两条深度最小的边,这种情况会被上述算法统计两次,因此我们需要将其多余计算的部分去除。即在两条路径的 L c a Lca Lca 处统计答案,两端所属的子树相同的路径可能形成这样的交,简单统计即可。
  • 时间复杂度 O ( N L o g N ) O(NLogN) O(NLogN)

【代码】

#include
using namespace std;
const int MAXN = 2e5 + 5;
const int MAXLOG = 20;
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]; ll ans;
vector <pair <int, int> > b[MAXN];
int cnt[MAXN], exclude[MAXN];
int depth[MAXN], father[MAXN][MAXLOG];
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] != fa) work(a[pos][i], 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 jump(int x, int y) {
	for (int i = MAXLOG - 1; i >= 0; i--)
		if (depth[father[x][i]] > depth[y]) x = father[x][i];
	return x;
}
void getans(int pos, int fa) {
	for (auto x : a[pos])
		if (x != fa) {
			getans(x, pos);
			cnt[pos] += cnt[x];
			exclude[pos] += exclude[x];
		}
	ans += (cnt[pos] - 1ll) * cnt[pos] / 2;
	ans -= (exclude[pos] - 1ll) * exclude[pos] / 2;
}
int main() {
	freopen("exercise.in", "r", stdin);
	freopen("exercise.out", "w", stdout);
	int n, m; read(n), read(m), m -= n - 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);
	}
	work(1, 0);
	for (int i = 1; i <= m; i++) {
		int x, y, z;
		read(x), read(y), z = lca(x, y);
		cnt[x]++, cnt[y]++, cnt[z] -= 2;
		exclude[x]++, exclude[jump(x, z)]--;
		exclude[y]++, exclude[jump(y, z)]--;
		if (x != z && y != z) b[z].emplace_back(x, y);
	}
	getans(1, 0);
	for (int i = 1; i <= n; i++) {
		map <pair <int, int>, int> exclude;
		for (auto c : b[i]) {
			int x = jump(c.first, i);
			int y = jump(c.second, i);
			if (x > y) swap(x, y);
			exclude[make_pair(x, y)]++;
		}
		for (auto x : exclude)
			ans -= (x.second - 1ll) * x.second / 2;
	}
	writeln(ans);
	return 0;
}

**【T3】**Train Tracking 2

【题目链接】

  • 点击打开链接

【题解链接】

  • 点击打开链接

【思路要点】

  • 考虑找到一个区间最大值的位置,将问题递归至两边处理。
  • 若当前区间 [ l , r ] [l,r] [l,r] 对应的 a l , a l + 1 , a l + 2 , . . . , a r − k + 1 a_l,a_{l+1},a_{l+2},...,a_{r-k+1} al,al+1,al+2,...,ark+1 均相等,那么问题显然可以用动态规划解决,即保证 [ l , r ] [l,r] [l,r] 中所有数都不超过最大值,且每连续的 k k k 个数中均存在一个最大值。
  • 否则,我们一定可以找到一个确定的位置,使得其一定是区间最大值。
  • 具体来说,令 M a x = m a x { a l , a l + 1 , a l + 2 , . . . , a r − k + 1 } Max=max\{a_l,a_{l+1},a_{l+2},...,a_{r-k+1}\} Max=max{al,al+1,al+2,...,ark+1} ,若 a l = M a x a_l=Max al=Max ,则表明 [ l , l + k − 1 ] [l,l+k-1] [l,l+k1] 中存在区间最大值,我们找到第一个使得 a p o s < M a x a_{pos}<Max apos<Max p o s pos pos ,那么 p o s − 1 pos-1 pos1 处的值一定是区间最大值 M a x Max Max
  • 否则,即 a l < M a x a_l<Max al<Max ,那么我们可以找到第一个使得 a p o s = M a x a_{pos}=Max apos=Max p o s pos pos p o s + k − 1 pos+k-1 pos+k1 处的值一定是区间最大值 M a x Max Max
  • 递归至两边处理即可。
  • 具体实现时需要使用 S T ST ST 表。
  • 时间复杂度 O ( N L o g N ) O(NLogN) O(NLogN)

【代码】

#include
using namespace std;
const int MAXN = 2e5 + 5;
const int P = 1e9 + 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("");
}
namespace rmq {
	const int MAXN = 1e5 + 5;
	const int MAXLOG = 18;
	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 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 n, k, a[MAXN];
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
int same(int len, int Max) {
	static int dp[MAXN];
	int sum = 0; dp[0] = 1; 
	for (int i = 1; i <= len; i++) {
		sum = 1ll * sum * Max % P;
		update(sum, dp[i - 1]);
		if (i - k - 1 >= 0) update(sum, P - 1ll * dp[i - k - 1] * power(Max, k) % P);
		dp[i] = sum;
	}
	int ans = 0;
	for (int i = 0; i <= len && i <= k - 1; i++)
		update(ans, 1ll * dp[len - i] * power(Max, i) % P);
	return ans;
}
int solve(int l, int r, int RMax) {
	int len = r - l + 1;
	if (len < k) return power(RMax + 1, len);
	int Max = rmq :: queryMax(l, r - k + 1);
	int Min = rmq :: queryMin(l, r - k + 1);
	if (Max == Min) return same(len, Max);
	if (a[l] == Max) {
		int ql = l, qr = r - k + 1;
		while (ql < qr) {
			int mid = (ql + qr) / 2;
			if (rmq :: queryMin(l, mid) != Max) qr = mid;
			else ql = mid + 1;
		}
		int pos = ql - 1;
		return 1ll * solve(l, pos - 1, Max) * solve(pos + 1, r, Max) % P;
	} else {
		int ql = l, qr = r - k + 1;
		while (ql < qr) {
			int mid = (ql + qr) / 2;
			if (rmq :: queryMax(l, mid) == Max) qr = mid;
			else ql = mid + 1;
		}
		int pos = ql + k - 1;
		return 1ll * solve(l, pos - 1, Max) * solve(pos + 1, r, Max) % P;
	}
}
int main() {
	freopen("tracking2.in", "r", stdin);
	freopen("tracking2.out", "w", stdout);
	read(n), read(k);
	for (int i = 1; i <= n - k + 1; i++) {
		read(a[i]);
		a[i] = 1e9 - a[i];
	}
	rmq :: init(a, n - k + 1);
	writeln(solve(1, n, 1e9));
	return 0;
}

你可能感兴趣的:(【OJ】USACO,【类型】做题记录,【比赛】USACO,【算法】差分与前缀和思想,【算法】动态规划,【数据结构】线段树,【数据结构】堆,【资料】好题,【算法】容斥原理,【数据结构】笛卡尔树,【算法】倍增与二分,【数据结构】ST表)