校内训练 2019-09-22

总结

没有任何感受。

唯一的想法是这个 USACO 的 Platinum 的难度大概连 NOIP 都不到吧。

另外这一场充分体现了我调代码水平的低下,一场 NOIP 难度都不到的模拟赛竟然能有一道题没有来得及看。

redistricting

题目传送门

http://192.168.21.187/contest/30/problem/1

http://47.100.137.146/contest/30/problem/1

题解

\(H\) 看成 \(1\)\(G\) 看做 \(-1\)。那么如果一段的和 \(>0\) 则不需要付出代价,如果一段的和 \(<0\) 则需要付出 \(1\) 的代价。

\(dp[i]\) 为前 \(i\) 个牛的最小损害,\(s_i\) 表示原串的前缀和,显然可以得到一个 dp 式子。

\[dp[i]=\min_{j=i-k+1}^{i-1} dp[j]+[s_j\geq s_i] \]

类似于 bzoj3831 [Poi2014]Little Bird 的做法,我们只需要对 \(dp[j],s_j\) 这个二元组建立单调队列就可以了。

因为笔者在考试的时候写了一半的线段树才想起来可以这样做,所以就干脆直接把线段树写完了。因此,下面是线段树的 \(O(n\log n)\) 的代码,对于线段树每个节点维护一个单调队列。

#include<bits/stdc++.h>

#define fec(i, x, y) (int i = head[x], y = g[i].to; i; i = g[i].ne, y = g[i].to)
#define dbg(...) fprintf(stderr, __VA_ARGS__)
#define File(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define fi first
#define se second
#define pb push_back

template<typename A, typename B> inline char smax(A &a, const B &b) {return a < b ? a = b , 1 : 0;}
template<typename A, typename B> inline char smin(A &a, const B &b) {return b < a ? a = b , 1 : 0;}

typedef long long ll; typedef unsigned long long ull; typedef std::pair<int, int> pii;

template<typename I> inline void read(I &x) {
	int f = 0, c;
	while (!isdigit(c = getchar())) c == '-' ? f = 1 : 0;
	x = c & 15;
	while (isdigit(c = getchar())) x = (x << 1) + (x << 3) + (c & 15);
	f ? x = -x : 0;
}

#define lc o << 1
#define rc o << 1 | 1

const int N = 3e5 + 7;
const int INF = 0x3f3f3f3f;

int n, m, T;
int s[N], dp[N];
char a[N];

struct Node {
	std::vector<int> q;
	int hd, tl;
} t[N << 3];

inline void build(int o, int L, int R) {
	t[o].hd = 1, t[o].tl = 0, t[o].q.pb(0);
	if (L == R) return;
	int M = (L + R) >> 1;
	build(lc, L, M), build(rc, M + 1, R);
}
inline void ins(int o, int L, int R, int x, int k) {
	while (t[o].hd <= t[o].tl && dp[k] <= dp[t[o].q[t[o].tl]]) --t[o].tl, t[o].q.pop_back();
	t[o].q.push_back(k), ++t[o].tl;
	if (L == R) return;
	int M = (L + R) >> 1;
	if (x <= M) ins(lc, L, M, x, k);
	else ins(rc, M + 1, R, x, k);
}
inline int qmin(int o, int L, int R, int l, int r, int k) {
	if (l > r) return INF;
	if (l <= L && R <= r) {
		while (t[o].hd <= t[o].tl && t[o].q[t[o].hd] < k - m) ++t[o].hd;
		if (t[o].hd > t[o].tl) return INF;
		else return dp[t[o].q[t[o].hd]];
	}
	int M = (L + R) >> 1;
	if (r <= M) return qmin(lc, L, M, l, r, k);
	if (l > M) return qmin(rc, M + 1, R, l, r, k);
	return std::min(qmin(lc, L, M, l, r, k), qmin(rc, M + 1, R, l, r, k));
}

inline void work() {
	build(1, 0, n << 1);
	ins(1, 0, n << 1, n, 0);
	for (int i = 1; i <= n; ++i) {
		dp[i] = std::min(qmin(1, 0, n << 1, 0, n + s[i] - 1, i), qmin(1, 0, n << 1, n + s[i], n << 1, i) + 1);
		ins(1, 0, n << 1, n + s[i], i);
	}
	printf("%d\n", dp[n]);
}

inline void init() {
	scanf("%d%d%s", &n, &m, a + 1);
	for (int i = 1; i <= n; ++i) s[i] = s[i - 1] + (a[i] == 'H' ? 1 : -1);
}

int main() {
#ifdef hzhkk
	freopen("hkk.in", "r", stdin);
#else
	File(redistricting);
#endif
	init();
	work();
	fclose(stdin), fclose(stdout);
	return 0;
}

exercise

题目传送门

http://192.168.21.187/contest/30/problem/2

http://47.100.137.146/contest/30/problem/2

题解

很容易发现,如果我们把每一条非常规路线看做一个路径覆盖,那么满足题目要求的路线一定是两个路径覆盖的交大于等于一条边的情况。

考虑如何求出这样的方案。

对于一个点,我们可以求出 \(f1[x]\) 为有多少个路径被包含在了 \(x\) 的子树中,\(f2[x]\) 为有多少路径存在一个端点在 \(x\) 的子树中。令 \(ss[x] = f2[x] - f1[x]\) 就可以表示有多少路径的一个端点在 \(x\) 的子树中,经过 \(x\) 后走向 \(x\) 外的子树。\(gg[x]\) 表示有多少路径从 \(x\) 的子树中走出,经过 \(x\) 后走向 \(fa[x]\) 自己或 \(fa[x]\) 的另一个子树。

回到原问题,对于 \(u\to lca \to v\) 这样的路径,和之有一条边的重合的路径的数量。考虑先算上所有可能的,再去掉不合法的。所有可能的,应该是所有存在一个端点在 \(lca\) 的子树中的路径,即 \(f2[lca]\)

去掉的,应该有 \(u\to lca \to v\) 上的每一个点 \(x\),以 \(fa[x]\)\(lca\) 并且不存在一个端点在 \(x\) 中的路径,这个可以用 \(f1[fa]-f1[x]-gg[x]\) 表示。但是对于 \(x\to lca\) 的路径上,最靠近 \(lca\) 的点 \(xx\) 和与之类似的定义的点 \(yy\),比较特殊,应该去掉的是以 \(fa[xx]\)\(lca\) 并且不存在一个端点在 \(xx\) 中且不在 \(yy\) 中的路径,然后还有从 \(lca\) 的子树走出,且起点不在 \(xx\)\(yy\) 的子树中的路径,这两个可以用 \(f1[p] - f1[xx] - f1[yy] - gg[xx] - gg[yy] + mp[xx, yy] + (ss[p] - ss[xx] - ss[yy] + gg[xx] + gg[yy])\) 来表示。其中 \(mp(x, y)\) 表示从 \(x\) 的子树走出,走向 \(fa[x]\) 再走到 \(y\) 的子树中的路径数量。

最后算出的路径数量应该会包含自己,所以需要 \(-1\)。求和后,因为每一个路径对被计算两次,需要除以 \(2\)

#include<bits/stdc++.h>

#define fec(i, x, y) (int i = head[x], y = g[i].to; i; i = g[i].ne, y = g[i].to)
#define dbg(...) fprintf(stderr, __VA_ARGS__)
#define File(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define fi first
#define se second
#define pb push_back

template<typename A, typename B> inline char smax(A &a, const B &b) {return a < b ? a = b , 1 : 0;}
template<typename A, typename B> inline char smin(A &a, const B &b) {return b < a ? a = b , 1 : 0;}

typedef long long ll; typedef unsigned long long ull; typedef std::pair<int, int> pii;

template<typename I> inline void read(I &x) {
	int f = 0, c;
	while (!isdigit(c = getchar())) c == '-' ? f = 1 : 0;
	x = c & 15;
	while (isdigit(c = getchar())) x = (x << 1) + (x << 3) + (c & 15);
	f ? x = -x : 0;
}

const int N = 2e5 + 7;

int n, m, dfc;
int siz[N], f[N], dep[N], son[N], dfn[N], pre[N], top[N];
int f1[N], f2[N], ss[N], dis[N], gg[N];
std::map<pii, int> mp;

struct Edge { int to, ne; } g[N << 1]; int head[N], tot;
inline void addedge(int x, int y) { g[++tot].to = y, g[tot].ne = head[x], head[x] = tot; }
inline void adde(int x, int y) { addedge(x, y), addedge(y, x); }

struct Trail { int x, y, p, xx, yy; } a[N];

inline void dfs1(int x, int fa = 0) {
	dep[x] = dep[fa] + 1, f[x] = fa, siz[x] = 1;
	for fec(i, x, y) if (y != fa) dfs1(y, x), siz[x] += siz[y], siz[y] >= siz[son[x]] && (son[x] = y);
}
inline void dfs2(int x, int pa) {
	top[x] = pa, dfn[x] = ++dfc, pre[dfc] = x;
	if (!son[x]) return; dfs2(son[x], pa);
	for fec(i, x, y) if (y != son[x] && y != f[x]) dfs2(y, y);
}
inline int lca(int x, int y) {
	while (top[x] != top[y]) dep[top[x]] > dep[top[y]] ? x = f[top[x]] : y = f[top[y]];
	return dep[x] < dep[y] ? x : y;
}
inline int gson(int x, int p) {
	int d = 0;
	while (top[x] != top[p]) d = top[x], x = f[d];
	return x != p ? son[p] : d;
}

inline void dfs3(int x, int fa = 0) {
	for fec(i, x, y) if (y != fa)
		dfs3(y, x), f1[x] += f1[y], f2[x] += f2[y];
	ss[x] = f2[x] - f1[x];
}
inline void dfs4(int x, int fa = 0) {
	if (fa) dis[x] = dis[fa] + f1[fa] - f1[x] - gg[x];
	for fec(i, x, y) if (y != fa) dfs4(y, x);
}

inline void work() {
	dfs1(1), dfs2(1, 1);
	for (int i = 1; i <= m; ++i) {
		const int &x = a[i].x, &y = a[i].y;
		int &p = a[i].p = lca(x, y), &xx = a[i].xx = gson(x, p), &yy = a[i].yy = gson(y, p);
		++mp[pii(xx, yy)], ++mp[pii(yy, xx)];
		++f1[p], ++f2[x], ++f2[y], --f2[p], ++gg[xx], ++gg[yy];
	}
	dfs3(1), dfs4(1);
	ll ans = 0;
	for (int i = 1; i <= m; ++i) {
		const int &x = a[i].x, &y = a[i].y, &p = a[i].p;
		int cnt = f2[p], xx = a[i].xx, yy = a[i].yy;
		if (xx) {
			cnt -= dis[x] - dis[xx];
			cnt -= f1[x];
		}
		if (yy) {
			cnt -= dis[y] - dis[yy];
			cnt -= f1[y];
		}
		if (xx && yy) cnt -= f1[p] - f1[xx] - f1[yy] - gg[xx] - gg[yy] + mp[pii(xx, yy)] + (ss[p] - ss[xx] - ss[yy] + gg[xx] + gg[yy]);
		else if (xx) cnt -= f1[p] - f1[xx] - gg[xx] + (ss[p] - ss[xx] + gg[xx]);
		else if (yy) cnt -= f1[p] - f1[yy] - gg[yy] + (ss[p] - ss[yy] + gg[yy]);
		ans += cnt - 1;
	}
	printf("%lld\n", ans / 2);
}

inline void init() {
	read(n), read(m), m -= n - 1;
	int x, y;
	for (int i = 1; i < n; ++i) read(x), read(y), adde(x, y);
	for (int i = 1; i <= m; ++i) read(a[i].x), read(a[i].y);
}

int main() {
#ifdef hzhkk
	freopen("hkk.in", "r", stdin);
#else
	File(exercise);
#endif
	init();
	work();
	fclose(stdin), fclose(stdout);
	return 0;
}

tracking2

题目传送门

http://192.168.21.187/contest/30/problem/3

http://47.100.137.146/contest/30/problem/3

题解

显然, 对于两个相邻的 \(c_1,c_2\),且 \(c_1 \neq c_2\),如果 \(c_1<c_2\),那么 \(a_1=c_1\);否则 \(a_2=c_2\)

我们可以把很多原来的 \(a\) 序列以一些已经被固定的数为间隔,划分为很多段,每一段的要求都是形如任意 \(k\) 个相邻的数的最小值为 \(y\),那么每一位就有 \(x=10^9 - y + 1\) 种取值。

考虑用 dp 完成这个要求。令 \(dp[i]\) 为前 \(i\) 个位置的答案。

第一个转移为 \(dp[i] = dp[i - 1] \cdot x\)

但是会有一个问题就是可能 \(i-k+1 .. i\) 这一段可能会全部大于 \(x\),就不满足 \(\min = x\) 了。

于是我们需要减掉不满足题意的部分, \(i-k+1 .. i\) 这一段可能会全部大于 \(x\),这里的方案为 \((x-1)^k\)。然后,因为 \(dp[i - 1]\) 是合法的,所以一定是一个 \(x\) 后面接 \(k-1\) 个大于 \(x\) 的数的情况。所以减去的部分是乘上 \(dp[i-k-1] \cdot (x-1)^k\) 的。

于是这一段的方案就是 \(dp[n]\)\(n\) 为这一段的长度。

#include<bits/stdc++.h>

#define fec(i, x, y) (int i = head[x], y = g[i].to; i; i = g[i].ne, y = g[i].to)
#define dbg(...) fprintf(stderr, __VA_ARGS__)
#define File(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define fi first
#define se second
#define pb push_back

template<typename A, typename B> inline char smax(A &a, const B &b) {return a < b ? a = b, 1 : 0;}
template<typename A, typename B> inline char smin(A &a, const B &b) {return b < a ? a = b, 1 : 0;}

typedef long long ll; typedef unsigned long long ull; typedef std::pair<int, int> pii;

template<typename I> inline void read(I &x) {
	int f = 0, c;
	while (!isdigit(c = getchar())) c == '-' ? f = 1 : 0;
	x = c & 15;
	while (isdigit(c = getchar())) x = (x << 1) + (x << 3) + (c & 15);
	f ? x = -x : 0;
}

const int N = 1e5 + 7;
const int P = 1e9 + 7;

int n, k, m;
int c[N], dp[N];

inline int smod(int x) { return x >= P ? x - P : x; }
inline void sadd(int &x, const int &y) { x += y; x >= P ? x -= P : x; }
inline int fpow(int x, int y) {
	int ans = 1;
	for (; y; y >>= 1, x = (ll)x * x % P) if (y & 1) ans = (ll)ans * x % P;
	return ans;
}

inline int DP(const int &n, const int &x) {
	if (n <= 0) return 1;
	dp[0] = 1;
	int thx = fpow(x - 1, k);
	for (int i = 1; i <= n; ++i) {
		dp[i] = (ll)dp[i - 1] * x % P;
		if (i >= k + 1) sadd(dp[i], P - (ll)dp[i - k - 1] * thx % P);
		else if (i >= k) sadd(dp[i], P - thx);
	}
	return dp[n];
}

inline void work() {
	int ans = 1;
	for (int i = 1; i <= m; ++i) {
		int l = i, r = i, st;
		while (r < n && c[r + 1] == c[l]) ++r;
		st = r - l + k;
		if (r < m && c[r + 1] > c[r]) st -= k;
		if (l > 1 && c[l - 1] > c[l]) st -= k;
		ans = (ll)ans * DP(st, 1e9 - c[l] + 1) % P;
		i = r;
	}
	printf("%d\n", ans);
}

inline void init() {
	read(n), read(k), m = n - k + 1;
	for (int i = 1; i <= m; ++i) read(c[i]);
}

int main() {
#ifdef hzhkk
	freopen("hkk.in", "r", stdin);
#else
	File(tracking2);
#endif
	init();
	work();
	fclose(stdin), fclose(stdout);
	return 0;
}
posted @ 2019-09-23 09:38  hankeke303  阅读(152)  评论(0编辑  收藏  举报