round 9

大寄。

我忏悔,我对不起组长 zwb,我对不起 AK 的 Cindy,对不起比我高的 lxf 和 lzm。

T3 数组开小。

T4 计算每天翘课所对应的答案时没用暴力,而是自作聪明的用了双指针(贪心),导致一分没有。

T5 没调出来,改成了暴力。

T6 暴力打错了,导致换根没想出来。

T1

一眼题。求出前缀和 \(sum_i\) 表示 \(0 \sim a_i\) 中有多少数没有出现在数列中。

由于 \(a\) 无序,所以先排一下序,此时 \(sum_i = sum_{i - 1} + a_i - a_{i - 1} - 1\)

然后对于输入的数 \(k\) 我们找到 \(sum\) 中第一个比它小的下标 \(p\),则答案一定在 \(a_p\)\(a_{p + 1}\) 之间,可以知道 \(0 \sim a_p\) 已有 \(sum_p\) 个数,所以答案为 \(a_p + k - sum_p\)

code
#include <cstdio>
#include <algorithm>
#define LL long long

using namespace std;

const int N = 1e5 + 5;

LL n, m, a[N], s[N], k;

int main() {
	scanf("%lld %lld", &n, &m);
	for (int i = 1; i <= n; i++)
		scanf("%lld", &a[i]);
	sort(a + 1, a + n + 1);
	for (int i = 1; i <= n; i++)
		s[i] = s[i - 1] + a[i] - a[i - 1] - 1;
	s[n + 1] = 1e18 - n;
	for (int i = 1; i <= m; i++) {
		scanf("%lld", &k);
		int p = lower_bound(s + 1, s + n + 2, k) - s - 1;
		printf("%lld\n", a[p] + k - s[p]);
	}
	return 0;
}

T2

计算在每个 '.' 处放时的答案,取最大值为答案。

对于每次,我们先判断能否放在此处。如果放上去后导致有白棋被黑棋吃掉就不能放。

如果可以放,那就计算能吃掉几个黑棋。

code
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 105;

int n, ans, tot;
char a[N];

int main() {
	scanf("%d %s", &n, a + 1);
	for (int i = 1; i <= n; i++) 
		if (a[i] == '.') {
			bool f1 = true, f2 = true;
			for (int j = i - 1; j; j--)
				if (a[j] == '.')
					break;
				else if (a[j] == 'B') {	//如果在遇到'.'之前遇到'B',说明这一段白棋左边为黑棋。 
					f1 = false;
					break;
				}
			for (int j = i + 1; j <= n; j++)
				if (a[j] == '.')
					break;
				else if (a[j] == 'B') {	//右边同理。 
					f2 = false;
					break;
				}
			if (f1 || f2) {	//如果两边有一边不是黑棋说明可以放白棋在此处。 
				int s = 0;
				for (int j = i - 1; j; j--)
					if (a[j] == 'B') //计算到左边下一个白棋中间的黑棋数。 
						s++;
					else if (a[j] == 'W') {	//到白棋,计算答案。 
						ans = max(ans, s);
						break;
					} else	//在到白棋之前遍历到'.'说明吃不掉。 
						break;
				s = 0;
				for (int j = i + 1; j <= n; j++)	//右边同理。 
					if (a[j] == 'B')
						s++;
					else if (a[j] == 'W') {
						ans = max(ans, s);
						break;
					} else
						break;
			}
		}
	printf("%d\n", ans);
	return 0;
}

T3

做了很久,即使做过。

最开始的时候我写了二维 dp,\(f_{i, j}\) 表示第 \(i\) 个位置作为 \(j\) 这个东西时的答案。(\(0,1,2\) 就为题目中的 '0', '1', '2',\(3\) 为 '*')。

但这样明显是错误的,因为转移时会导致一些不符合要求的答案被计算出来,还会算重复一些答案。

于是我就想到如果在此基础上枚举 \(i\) 周围两个点是否是 '*' 就可以了。

如果当前位置是 '0', 那么 \(f_{i, 0, 0, 0} = f_{i - 1, 0, 0, 0} + f_{i - 1, 1, 1, 0}\)

如果当前位置是 '1',那么 \(f_{i, 1, 0, 1} = f_{i - 1, 0, 0, 0} + f_{i - 1, 1, 1, 0}, f_{i, 1, 1, 0} = f_{i - 1, 3, 1, 0} + f_{i - 1, 3, 0, 0}\)

剩下两种情况可以照此推推。

如果是 '?',就分别将它当做以上 \(4\) 种计算。

特殊地,如果 \(i = 1\),那么第 \(3\) 维一定为 \(0\),因为前面不能放东西,初始化时注意;同理 \(i = n\) 时,第 \(4\) 维一定为 \(0\),所以答案为 \(f_{n, 0, 0, 0} + f_{n, 1, 1, 0} + f_{n, 3, 1, 0} + f_{n, 3, 0, 0}\)

别忘了取模。

code
#include <cstdio>
#include <cstring>
#define LL long long

const int N = 1e6 + 5;
const LL Mod = 1e9 + 7;

int n;
LL f[N][5][2][2]; 
char a[N];

int main() {
	while (~scanf("%s", a + 1)) {
		n = strlen(a + 1);
		for (int i = 0; i <= n; i++)
			for (int j = 0; j < 4; j++)
				f[i][j][0][0] = f[i][j][0][1] = f[i][j][1][0] = f[i][j][1][1] = 0;
		if (a[1] == '0' || a[1] == '?')
			f[1][0][0][0] = 1;
		if (a[1] == '1' || a[1] == '?')
			f[1][1][0][1] = 1;
		if (a[1] == '*' || a[1] == '?') {
			f[1][3][0][0] = 1;
			f[1][3][0][1] = 1;
		}
		for (int i = 2; i <= n; i++) {
			if (a[i] == '0' || a[i] == '?')
				f[i][0][0][0] = (f[i - 1][0][0][0] + f[i - 1][1][1][0]) % Mod;
			if (a[i] == '1' || a[i] == '?') {
				f[i][1][1][0] = (f[i - 1][3][1][0] + f[i - 1][3][0][0]) % Mod;
				f[i][1][0][1] = (f[i - 1][0][0][0] + f[i - 1][1][1][0]) % Mod;
			}
			if (a[i] == '2' || a[i] == '?')
				f[i][2][1][1] = (f[i - 1][3][1][0] + f[i - 1][3][0][0]) % Mod;
			if (a[i] == '*' || a[i] == '?') {
				f[i][3][0][0] = (f[i - 1][1][0][1] + f[i - 1][2][1][1]) % Mod;
				f[i][3][1][0] = (f[i - 1][3][0][1] + f[i - 1][3][1][1]) % Mod;
				f[i][3][0][1] = (f[i - 1][1][0][1] + f[i - 1][2][1][1]) % Mod;
				f[i][3][1][1] = (f[i - 1][3][0][1] + f[i - 1][3][1][1]) % Mod;
			}
		}
		printf("%lld\n", (f[n][0][0][0] + f[n][1][1][0] + f[n][3][0][0] + f[n][3][1][0]) % Mod);
	}
	return 0;
}

T4

背包。

我们先计算出来如果不逃课,需要在教学楼待多久,然后再通过 dp 求出通过逃课最多可以少待多久,相减就是答案。

我们可以在输入时把当前第 \(i\) 天哪些时间在上课放到一个数组中,那么 \(x_{tot} - x_1 + 1\) 就是这一天本来要在教学楼待多久。

求到了这个之后我们继续枚举这一天逃每个数量的课时,最多减少多少在教学楼的时间,用 \(s_{i, j}\) 表示第 \(i\) 天逃 \(j\) 节课最多减少多少时间。至于求发,暴力即可(不要写贪心)。

然后就是 dp,考试的时候没有注意到是背包,但还是很自然地打了出来(虽然是二维背包)。

\(f_{i, j}\) 表示前 \(i\) 天逃 \(j\) 节课的答案,很明显:

\[f_{i, j} = \max_{k = 0}^{j}(s_{i, k} + f_{i - 1, j - k}) \]

最后求到 \(f\) 中最大值与总时间相减即可。

code
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 505;

int n, m, k, s[N][N], f[N][N], x[N], tot, ans, res;
char a[N];

int main() {
	scanf("%d %d %d", &n, &m, &k);
	for (int i = 1; i <= n; i++) {
		scanf(" %s", a + 1);
		tot = 0;
		for (int j = 1; j <= m; j++)
			if (a[j] == '1')
				x[++tot] = j;
		if (tot)
			ans += x[tot] - x[1] + 1;
		for (int l = 1; l <= tot; l++)
			for (int r = l; r <= tot; r++)
				s[i][tot - (r - l + 1)] = max(s[i][tot - (r - l + 1)], x[tot] - x[1] + 1 - (x[r] - x[l] + 1));
		for (int j = 0; j <= k; j++) {
			for (int k = 0; k <= j; k++)
				f[i][j] = max(f[i][j], s[i][k] + f[i - 1][j - k]);
			res = max(res, f[i][j]);
		}
	}
	printf("%d\n", ans - res);
	return 0;
}

T5

有源汇上下界最小流。在考场上不知道为什么没调出来。

对于每横排,至少种 \(r_i\) 棵树,所以向源点连一条上界为 \(inf\),下界为 \(r_i\)的边,对于每竖排,至少种 \(c_i\) 棵树,所以向汇点连一条上界为 \(inf\) 下界为 \(c_i\) 的边。

然后枚举每个点,如果没被焦化,说明可以在此种树。假设这个点是 \((i, j)\),那么第 \(i\) 行就向第 \(j\) 行建一条上界为 \(1\),下界为 \(0\) 的边。

对于无解,我们只需让每个点只要能种就都种,如果这样都不能满足,就肯定无解。

code
#include <cstdio>
#include <queue>
#include <algorithm>
#include <iostream> 
#define LL long long
#define inf 1e15

using namespace std;

const int N = 1e5 + 5, M = 1e6 + 5;

struct Edge {
	int to, next;
	LL c1, c2, c;
};
struct node {
	int n, m, head[N], tot, h[N], now[N];
	LL flow;
	Edge edge[M << 1];
	queue<int> q;
	inline node() { tot = 1; }
	inline void add(int u, int v, LL c1, LL c2, LL c) {
		edge[++tot].to = v, edge[tot].c1 = c1, edge[tot].c2 = c2, edge[tot].c = c;
			edge[tot].next = head[u], head[u] = tot;
	}
	inline bool bfs(int s, int t) {
		while (!q.empty())
			q.pop();
		for (int i = 1; i <= n; i++)
			h[i] = 0, now[i] = head[i];
		h[s] = 1;
		q.push(s);
		while (!q.empty()) {
			int u = q.front();
			q.pop();
			for (int i = head[u]; i; i = edge[i].next) {
				int v = edge[i].to;
				LL c = edge[i].c;
				if (c && !h[v]) {
					h[v] = h[u] + 1;
					q.push(v);
					if (v == t)
						return true;
				}
			}
		}
		return false;
	}
	inline int dfs(int u, LL flo, int t) {
		if (u == t)
			return flo;
		LL x = 0;
		for (int i = now[u]; i; i = edge[i].next) {
			now[u] = i;
			int v = edge[i].to;
			LL c = edge[i].c;
			if (c && h[u] + 1 == h[v]) {
				LL fl = dfs(v, min(flo - x, c), t);
				edge[i].c -= fl;
				edge[i ^ 1].c += fl;
				x += fl;
				if (flo == x)
					return flo;
			}
		}
		return x;
	} 
	inline void dinic(int s, int t) {
		flow = 0;
		while (bfs(s, t))
			flow += dfs(s, inf, t);
	}
} F;
int n, m, k, s, t, S, T, x[N], y[N];
LL ans, c[N];
bool flag[105][105];

int main() {
	scanf("%d %d %d", &n, &m, &k);
	s = n + m + 1, t = s + 1;
	S = t + 1, T = S + 1;
	F.add(s, t, 0, 0, 0), F.add(t, s, 0, 0, inf);
	LL c1, c2;
	for (int i = 1; i <= n; i++) {
		scanf("%d", &x[i]);
		c[s] += x[i], c[i] -= x[i];
		F.add(s, i, x[i], inf, inf - x[i]), F.add(i, s, 0, 0, 0);
	}
	for (int i = 1; i <= m; i++) {
		scanf("%d", &y[i]);
		c[i + n] += y[i], c[t] -= y[i];
		F.add(i + n, t, y[i], inf, inf - y[i]), F.add(t, i + n, 0, 0, 0);
	}
	for (int i = 1, xx, yy; i <= k; i++) {	//表记焦化的土地。 
		scanf("%d %d", &xx, &yy);
		flag[xx][yy] = true;
	}
	for (int i = 1; i <= n; i++) {
		int s = 0;
		for (int j = 1; j <= m; j++)
			s += !flag[i][j];
		if (s < x[i])
			return puts("Unable to plant"), 0;
	}
	for (int j = 1; j <= m; j++) {
		int s = 0;
		for (int i = 1; i <= n; i++)
			s += !flag[i][j];
		if (s < y[j])
			return puts("Unable to plant"), 0;
	}
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			if (!flag[i][j])	//如果没被焦化,建边。 
				F.add(i, j + n, 0, 1, 1), F.add(j + n, i, 0, 0, 0);
	for (int i = 1; i <= t; i++)
		if (c[i] > 0)
			F.add(i, T, 0, 0, c[i]), F.add(T, i, 0, 0, 0); 
		else if (c[i] < 0)
			F.add(S, i, 0, 0, -c[i]), F.add(i, S, 0, 0, 0);
	F.n = T;	//板子。 
	F.dinic(S, T);
	ans = F.edge[2].c;
	F.edge[2].c = F.edge[3].c = 0;
	F.dinic(t, s);
	printf("%lld\n", ans - F.flow);
	return 0;
}

T6

换根 dp。

首先明确暴力怎么打,对于根节点,我们求出到所有标记了的点并返回根节点所需时间,存在 \(f\) 里。

因为最后不需要回到起点,所以用数组 \(g\) 求出根节点离最远的标记了的点需要所需时间。

那么对于根节点答案就是 \(f_i - g_i\)

所以 \(O(n^2)\) 的做法就出来了。

每个点当根节点都算一遍。

但是既然都这样了,换根就可以换了。

对于当前根节点 \(u\) 我们枚举它的子节点,考虑让子节点变成根。

我们先消除 \(v\) 对于 \(u\) 的影响,再用 \(u\) 更新 \(v\) 的答案。

但是由于需要用 \(g_u\) 更新 \(g_v\),但如果 \(g_u\) 就是用 \(g_v\) 转移得到的,所以我们还要记录每个点是由那个点的 \(g\) 转移达得到最大值,并记录次大值。

code
#include <cstdio>
#include <algorithm>
#define LL long long

using namespace std;

const int N = 5e5 + 5;

int n, head[N], tot, k;
LL f[N], siz[N], g[N][2], id[N][2], ans[N];
bool flag[N];
struct Edge {
	int to, next;
	LL w;
} edge[N << 1];

inline void add(int u, int v, LL w) {
	edge[++tot].to = v, edge[tot].w = w;
	edge[tot].next = head[u], head[u] = tot;
}

inline void dfs1(int x, int u) {
	if (flag[u])
		siz[u] = 1;
	for (int i = head[u]; i; i = edge[i].next) {
		int v = edge[i].to;
		LL w = edge[i].w;
		if (v == x)
			continue;
		dfs1(u, v);
		siz[u] += siz[v];	//siz用来统计以当前节点为根的子树下有多少标记点。 
		if (siz[v]) {	//如果有标记点才转移。 
			f[u] += f[v] + w;
			if (g[u][0] < g[v][0] + w)
				g[u][1] = g[u][0], id[u][1] = id[u][0], g[u][0] = g[v][0] + w, id[u][0] = v;
			else if (g[u][1] < g[v][0] + w)
				g[u][1] = g[v][0] + w, id[u][1] = v;
		}
	}
}

inline void dfs2(int x, int u) {
	ans[u] = (f[u] << 1) - g[u][0];
	for (int i = head[u]; i; i = edge[i].next) {
		int v = edge[i].to;
		LL w = edge[i].w;
		if (v == x)
			continue;
		LL fu = f[u], su = siz[u], gu0 = g[u][0], idu0 = id[u][0];
		if (siz[v])	//如果siz[v]有值,说明对u产生了影响,消除它。 
			f[u] -= f[v] + w, siz[u] -= siz[v];
		if (id[u][0] == v)	//如果让给g[u]取最大值的是v,就将g[u]换成次大值。 
			g[u][0] = g[u][1], id[u][0] = id[u][1];
		siz[v] += siz[u];
		if (siz[u]) {
			f[v] += f[u] + w;
			if (g[v][0] < g[u][0] + w)
				g[v][1] = g[v][0], id[v][1] = id[v][0], g[v][0] = g[u][0] + w, id[v][0] = u;
			else if (g[v][1] < g[u][0] + w)
				g[v][1] = g[u][0] + w, id[v][1] = u;
		}
		dfs2(u, v);
		f[u] = fu, siz[u] = su, g[u][0] = gu0, id[u][0] = idu0;
	}
}

int main() {
	scanf("%d %d", &n, &k);
	for (int i = 1, u, v, w; i < n; i++) {
		scanf("%d %d %d", &u, &v, &w);
		add(u, v, w), add(v, u, w);
	}
	for (int i = 1, x; i <= k; i++) {
		scanf("%d", &x);
		flag[x] = true; //标记。 
	}
	dfs1(0, 1);
	dfs2(0, 1);
	for (int i = 1; i <= n; i++)
		printf("%lld\n", ans[i]);
	return 0;
}
posted @ 2023-08-22 16:11  zhy。  阅读(12)  评论(0编辑  收藏  举报