【CodeForces】Codeforces Round 621

比赛链接

点击打开链接

官方题解

点击打开链接

Problem A. Cow and Haybales

按照题意模拟即可。

单组数据时间复杂度 O ( N + D ) O(N+D)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 105;
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, d, a[MAXN];
int main() {
	int T; read(T);
	while (T--) {
		read(n), read(d);
		for (int i = 1; i <= n; i++)
			read(a[i]);
		for (int i = 2; i <= n; i++) {
			while (a[i] && d >= i - 1) {
				d -= i - 1;
				a[i]--, a[1]++;
			}
		}
		cout << a[1] << endl;
	}
	return 0;
}

Problem B. Cow and Friend

考虑跳跃总距离 S S 应满足的条件。

若跳跃次数为 1 1 ,则应有 S = x S=x ,否则,应有 S x S\geq x
因此,判断答案是否为 1 1 ,否则取最大的跳跃距离计算答案即可。

单组数据时间复杂度 O ( N ) O(N)

#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;
}
int main() {
	int T; read(T);
	while (T--) {
		int n, m; read(n), read(m);
		int Max = 0, ans = INT_MAX;
		for (int i = 1; i <= n; i++) {
			int x; read(x);
			chkmax(Max, x);
			if (x == m) ans = 1;
		}
		chkmin(ans, max(2, (m - 1) / Max + 1));
		cout << ans << endl;
	}
	return 0;
}

Problem C. Cow and Message

显然,满足题目中要求的子序列由其长度和前两个字符的位置唯一确定。
因此,任意出现次数为 x x 的字符串 S S 的长度不小于 2 2 的前缀 S S' 的出现次数一定不少于 x x

由此,我们可以只考虑长度为 1 , 2 1,2 的字符串。
则用前缀和简单统计每个字符串分别的出现次数即可。

时间复杂度 O ( N σ + σ 2 ) O(N\sigma+\sigma^2)

#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;
}
char s[MAXN];
int pre[MAXN][26]; ll cnt[26][26];
int main() {
	scanf("\n%s", s + 1);
	int n = strlen(s + 1);
	for (int i = 1; i <= n; i++) {
		memcpy(pre[i], pre[i - 1], sizeof(pre[i - 1]));
		for (int j = 0; j <= 25; j++)
			cnt[j][s[i] - 'a'] += pre[i][j];
		pre[i][s[i] - 'a']++;
	}
	ll ans = 0;
	for (int i = 0; i <= 25; i++)
		chkmax(ans, 0ll + pre[n][i]);
	for (int i = 0; i <= 25; i++)
	for (int j = 0; j <= 25; j++)
		chkmax(ans, cnt[i][j]);
	cout << ans << endl;
	return 0;
}

Problem D. Cow and Fields

首先用 BFS 求出各个点到 1 1 号点的最短路 f i f_i 和到 N N 号点的最短路 g i g_i

考虑连接关键点 x , y x,y 后最短路的变化,显然,若不经过边 ( x , y ) (x,y) 最短路不变,否则,最短路应为
1 + M i n { f x + g y , f y + g x } 1+Min\{f_x+g_y,f_y+g_x\}

注意到若 f x f y , g x g y f_x\geq f_y,g_x\geq g_y ,连接 ( x , y ) (x,y) 不会使最短路发生变化,我们可以将上式稍作修改:
1 + M i n { f x , f y } + M i n { g x , g y } 1+Min\{f_x,f_y\}+Min\{g_x,g_y\}

我们需要找到最大化这个式子的 ( x , y ) (x,y) ,并将得到的值与原有最短路取最小值。则可以将所有点按照 f i f_i 排序,从大到小枚举 M i n { f x , f y } Min\{f_x,f_y\} ,并维护 g i g_i 的次大值用于更新答案。

时间复杂度 O ( N L o g N + M ) O(NLogN+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;
}
int f[MAXN], g[MAXN];
int n, m, k, key[MAXN];
vector <int> a[MAXN];
void bfs(int pos, int *dist) {
	static int q[MAXN];
	for (int i = 1; i <= n; i++)
		dist[i] = -1;
	dist[pos] = 0;
	int l = 0, r = 0; q[0] = pos;
	while (l <= r) {
		int tmp = q[l++];
		for (auto x : a[tmp])
			if (dist[x] == -1) {
				dist[x] = dist[tmp] + 1;
				q[++r] = x;
			}
	}
}
int main() {
	read(n), read(m), read(k);
	for (int i = 1; i <= k; i++)
		read(key[i]);
	for (int i = 1; i <= m; i++) {
		int x, y; read(x), read(y);
		a[x].push_back(y);
		a[y].push_back(x);
	}
	bfs(1, f);
	bfs(n, g);
	sort(key + 1, key + k + 1, [&] (int x, int y) {return f[x] > f[y]; });
	int Max = 0, Nax = 0, ans = 0;
	for (int i = 1; i <= k; i++) {
		int pos = key[i];
		if (g[pos] > Max) {
			Nax = Max;
			Max = g[pos];
		} else chkmax(Nax, g[pos]);
		if (i != 1) chkmax(ans, f[pos] + Nax + 1);
	}
	chkmin(ans, f[n]);
	cout << ans << endl;
	return 0;
}

Problem E. Cow and Treats

每个元素有三个属性,颜色 f i f_i ,从左到右行走的距离 l i l_i ,从右到左行走的距离 r i r_i
我们希望找到大小之和最大的两个集合 S , T S,T ,满足同一集合中的元素颜色两两不同,且
M a x i S { l i } + M a x i T { r i } N Max_{i\in S}\{l_i\}+Max_{i\in T}\{r_i\}\leq N

则首先枚举 l i = M a x x S { l x } l_i=Max_{x\in S}\{l_x\} ,那么,元素 j j 可以被归入 S S 中需要满足 l j l i l_j\leq l_i ,可以被归入 T T 中需要满足 r j + l i N r_j+l_i\leq N 。并且由于我们希望最大化选出的元素个数,问题对于每一种颜色的独立的,简单计算即可。

时间复杂度 O ( N 2 ) O(N^2)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5005;
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 n, m, s[MAXN]; pair <int, int> ans;
vector <pair <int, int>> a[MAXN];
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
void update(pair <int, int> &x, pair <int, int> y) {
	if (y.first > x.first) x = y;
	else if (y.first == x.first) update(x.second, y.second);
}
int main() {
	read(n), read(m);
	for (int i = 1; i <= n; i++)
		read(s[i]);
	for (int i = 1; i <= m; i++) {
		int x, y; read(x), read(y);
		pair <int, int> res = make_pair(-1, -1);
		for (int j = 1, k = y; j <= n; j++)
			if (s[j] == x && --k == 0) {
				res.first = j;
				break;
			}
		for (int j = n, k = y; j >= 1; j--)
			if (s[j] == x && --k == 0) {
				res.second = j;
				break;
			}
		if (res.first != -1) a[x].push_back(res);
	}
	pair <int, int> ans = make_pair(0, 1);
	for (int i = 1; i <= n; i++)
		if (a[i].size()) {
			ans.first++;
			ans.second = 1ll * ans.second * a[i].size() % P;
		}
	for (int i = 1; i <= n; i++)
	for (auto x : a[i]) {
		int pos = x.first, cnt = 0;
		pair <int, int> cur = make_pair(1, 1);
		for (auto y : a[i])
			if (y.first != pos && y.second > pos) cnt++;
		if (cnt != 0) {
			cur.first++;
			cur.second = cnt;
		}
		for (int j = 1; j <= n; j++) {
			if (i == j) continue;
			int l = 0, r = 0, m = 0;
			for (auto y : a[j])
				if (y.first < pos && y.second > pos) m++;
				else if (y.first < pos) l++;
				else if (y.second > pos) r++;
			int ways = 0;
			ways += l * r;
			ways += l * m;
			ways += r * m;
			ways += m * (m - 1);
			if (ways) {
				cur.first += 2;
				cur.second = 1ll * cur.second * ways % P;
			} else if (l + r + m) {
				cur.first += 1;
				cur.second = 1ll * cur.second * (l + r + 2 * m) % P;
			}
		}
		update(ans, cur);
	}
	cout << ans.first << ' ' << ans.second << endl;
	return 0;
}

Problem F. Cow and Vacation

首先考虑一个 O ( N V ) O(NV) 的解法。

我们称还能行走的步数为当前的活力 e e
求出到每个点 i i 最近的补给站的距离 d i d_i ,经过点 i i 时,若 e [ d i , k d i ] e\in[d_i,k-d_i] ,则可以将活力补足至 k d i k-d_i 。由此,我们从起点出发,走向终点,尝试在途经的每一个节点补充活力,可以得到一个 O ( N V ) O(NV) 的算法。

考虑如何优化,我们首先特判掉 x x 可以不经过补给站到达 y y 的情况。

对于询问 ( x , y ) (x,y) ,考虑求出上述解法中, x x y y 的路径上到达的任意一个补给站 u u y y x x 的路径上到达的任意一个补给站 v v 。若求得了这两个补给站,则询问 ( x , y ) (x,y) 将等价于询问 ( u , v ) (u,v)
由上面的算法的正确性,不难证明这两个询问的等价性。

考虑走出 x x 步后,到达了点 i i ,我们在何种情况下会考虑去补给站。则应为
k x [ d i , k d i ] k-x\in[d_i,k-d_i]

注意到该区间若有意义,应当有 d i k 2 , k d i k 2 d_i\leq\frac{k}{2},k-d_i\geq \frac{k}{2}
因此若 x k 2 x\leq \frac{k}{2} ,应有 k x k d i k-x\leq k-d_i ,否则,即 x k 2 x\geq \frac{k}{2} ,应有 k x d i k-x\geq d_i
注意到有
k x k d i k ( x + 1 ) k d i k-x\leq k-d_i\Rightarrow k-(x+1)\leq k-d_i
k x d i k ( x 1 ) d i k-x\geq d_i\Rightarrow k-(x-1)\geq d_i

因此,若存在可以到达的补给站,一定存在走出恰好 k 2 \frac{k}{2} 步后可以到达的补给站。

因此,将所有边倍长,使得 k k 成为偶数,然后对于询问 ( x , y ) (x,y) ,令 x , y x,y 分别靠近 k 2 \frac{k}{2} 步,再走到最近的补给站 u , v u,v 即可。

补给站之间的连通性可以简单通过 BFS 求得。

时间复杂度 O ( ( N + V ) L o g N ) O((N+V)LogN)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 4e5 + 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 depth[MAXN], father[MAXN][MAXLOG];
vector <int> a[MAXN];
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 climb(int x, int y) {
	for (int i = MAXLOG - 1; i >= 0; i--)
		if (y & (1 << i)) x = father[x][i];
	return x;
}
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 n, m, l, r, k, q[MAXN], f[MAXN], dist[MAXN];
int find(int x) {
	if (f[x] == x) return x;
	else return f[x] = find(f[x]);
}
int main() {
	read(n), read(k), read(r), l = 1;
	for (int i = 1; i <= n - 1; i++) {
		int x, y; read(x), read(y);
		a[x].push_back(n + i);
		a[n + i].push_back(x);
		a[y].push_back(n + i);
		a[n + i].push_back(y);
	}
	n += n - 1;
	work(1, 0);
	for (int i = 1; i <= n; i++)
		f[i] = i;
	memset(dist, -1, sizeof(dist));
	for (int i = 1; i <= r; i++)
		read(q[i]), dist[q[i]] = 0;
	while (l <= r) {
		int pos = q[l++];
		if (dist[pos] == k) continue;
		for (auto x : a[pos]) {
			if (dist[x] == -1) {
				dist[x] = dist[pos] + 1;
				q[++r] = x;
			}
			f[find(x)] = find(pos);
		}
	}
	read(m);
	while (m--) {
		int x, y; read(x), read(y);
		int z = lca(x, y);
		if (depth[x] + depth[y] - 2 * depth[z] <= 2 * k) puts("YES");
		else {
			if (depth[x] - depth[z] >= k) x = climb(x, k);
			else x = climb(y, depth[x] + depth[y] - 2 * depth[z] - k);
			if (depth[y] - depth[z] >= k) y = climb(y, k);
			else y = climb(x, depth[x] + depth[y] - 2 * depth[z] - k);
			if (find(x) == find(y)) puts("YES");
			else puts("NO");
		}
	}
	return 0;
}

Problem G. Cow and Exercise

这是一道先有解法,后有题面的题。

考虑最小费用流的标准型线性规划:
令点集为 V V ,边集为 E E ,源点为 S S ,汇点为 T T ,流量为 F l o w Flow
令边 ( u , v ) (u,v) 的流量为 x u , v x_{u,v} ,费用为 w u , v w_{u,v} ,容量限制为 c u , v c_{u,v}
最小化
( u , v ) E x u , v w u , v \sum_{(u,v)\in E}x_{u,v}w_{u,v}
满足约束
( v , u ) E x v , u ( u , v ) E x u , v = 0   ( u V / { S , T } ) \sum_{(v,u)\in E}x_{v,u}-\sum_{(u,v)\in E}x_{u,v}=0\ (u\in V/\{S,T\})
( v , u ) E x v , u ( u , v ) E x u , v = F l o w   ( u = S ) \sum_{(v,u)\in E}x_{v,u}-\sum_{(u,v)\in E}x_{u,v}=-Flow\ (u=S)
( v , u ) E x v , u ( u , v ) E x u , v = F l o w   ( u = T ) \sum_{(v,u)\in E}x_{v,u}-\sum_{(u,v)\in E}x_{u,v}=Flow\ (u=T)
0 x u , v c u , v   ( ( u , v ) E ) 0\leq x_{u,v}\leq c_{u,v}\ ((u,v)\in E)

考虑其对偶线性规划,考虑为流量平衡限制设置变量 y u y_u ,为容量限制设置变量 z u , v z_{u,v} ,则有:
最大化
F l o w ( y T y S ) ( u , v ) E z u , v c u , v Flow(y_T-y_S)-\sum_{(u,v)\in E}z_{u,v}c_{u,v}
满足约束
y u + w u , v + z u , v y v   ( ( u , v ) E ) y_u+w_{u,v}+z_{u,v}\geq y_v\ ((u,v)\in E)
z u , v 0 ( ( u , v ) E ) z_{u,v}\geq 0((u,v)\in E)
y u 0 ( u V ) y_{u}\geq 0(u\in V)

不难发现,若将 c u , v c_{u,v} 全部设置为 1 1 ,得到的线性规划与题面所要求的内容一致。
其中 y T y S y_T-y_S 即为 S S T T 的最短路, ( u , v ) E z u , v c u , v \sum_{(u,v)\in E}z_{u,v}c_{u,v} 即为修改边权的代价总和。

因此,在给定的图上求出所有流量 F l o w Flow 对应的最小费用流 C o s t Cost ,也即是
F l o w ( y T y S ) ( u , v ) E z u , v c u , v Flow(y_T-y_S)-\sum_{(u,v)\in E}z_{u,v}c_{u,v} 的最大值。

从而有限制 ( y T y S ) C o s t + ( u , v ) E z u , v c u , v F l o w (y_T-y_S)\leq \frac{Cost+\sum_{(u,v)\in E}z_{u,v}c_{u,v}}{Flow} ,取所有限制中最紧的作为答案即可。

时间复杂度 O ( M i n c o s t F l o w ( N , M ) + N × Q ) O(MincostFlow(N,M)+N\times Q)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 55;
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 MincostFlow {
	const int MAXP = 55;
	const int MAXQ = 2e5 + 5;
	const int INF  = 2e9;
	struct edge {int dest, flow, pos, cost; };
	vector <edge> a[MAXP];
	int n, m, s, t, tot, flow, cost;
	int dist[MAXP], path[MAXP], home[MAXP];
	void FlowPath() {
		int p = t, ans = INF;
		while (p != s) {
			ans = min(ans, a[path[p]][home[p]].flow);
			p = path[p];
		}
		flow += ans;
		cost += ans * dist[t];
		p = t;
		while (p != s) {
			a[path[p]][home[p]].flow -= ans;
			a[p][a[path[p]][home[p]].pos].flow += ans;
			p = path[p];
		}
	}
	bool spfa() {
		static int q[MAXQ];
		static bool inq[MAXP];
		static int l = 0, r = 0;
		for (int i = 0; i <= r; i++)
			dist[q[i]] = INF;
		q[l = r = 0] = s, dist[s] = 0, inq[s] = true;
		while (l <= r) {
			int tmp = q[l];
			for (unsigned i = 0; i < a[tmp].size(); i++)
				if (a[tmp][i].flow != 0 && dist[tmp] + a[tmp][i].cost < dist[a[tmp][i].dest]) {
					dist[a[tmp][i].dest] = dist[tmp] + a[tmp][i].cost;
					path[a[tmp][i].dest] = tmp;
					home[a[tmp][i].dest] = i;
					if (!inq[a[tmp][i].dest]) {
						q[++r] = a[tmp][i].dest;
						inq[q[r]] = true;
					}
				}
			l++, inq[tmp] = false;
		}
		return dist[t] != INF;
	}
	void addedge(int x, int y, int z, int c) {
		a[x].push_back((edge){y, z, a[y].size(), c});
		a[y].push_back((edge){x, 0, a[x].size() - 1, -c});
	}
	void work(int n, int *res) {
		for (int i = 1; i <= n; i++)
			dist[i] = INF;
		while (spfa()) {
			FlowPath();
			res[flow] = cost;
		}
	}
}
int res[MAXN];
int main() {
	using namespace MincostFlow;
	read(n), read(m), s = 1, t = n;
	for (int i = 1; i <= m; i++) {
		int x, y, z; read(x), read(y), read(z);
		addedge(x, y, 1, z);
	}
	work(n, res);
	int q; read(q);
	while (q--) {
		int c; read(c); double ans = 1e99;
		for (int i = 1; i <= flow; i++)
			chkmin(ans, 1.0 * (c + res[i]) / i);
		printf("%.10lf\n", ans);
	}
	return 0;
}
发布了813 篇原创文章 · 获赞 93 · 访问量 18万+

猜你喜欢

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