Codeforces Round #753 (Div. 3) 题解 完整A~H

Codeforces Round #753 (Div. 3) 题解

说明

本次开始,我的代码采用编译预处理指令来处理freopen输入重定向问题,我不再对freopen语句进行注释处理,改为如下所示的代码段:

#ifndef ONLINE_JUDGE
	freopen("in.txt", "r", stdin);
#endif

上述代码段在本机运行将会将输入重定向到文本文件,而在提交的时候该段失效。如果不能理解,可以直接将这一段删去,不会影响代码运行。

A. Linear Keyboard

题意

有一个键盘,键位呈直线分布。给出字母在直线上的位置,求输入一个字符串需要移动多少次。移动的次数定义为相邻两个字符在键盘上的距离之和,即移动次数 T = ∑ i = 1 n − 1 ∣ p s i − p s i + 1 ∣ T=\sum_{i=1}^{n-1}|p_{s_i}-p_{s_{i+1}}| T=i=1n1psipsi+1,其中 s i s_i si表示字符串中第 i i i个元素, p c p_c pc表示字母 c c c在键盘上是第几个。

思路

模拟题,读入字母键位排布之后,预处理每个字母的位置(也就是建立字母与下标的一一映射关系),然后对于每两个相邻字母,代入上述式子求值即可。

其实在本题数据量下,不预处理位置也不会超时。

时间复杂度

O ( n ) O(n) O(n)

AC代码
ProblemLangVerdictTimeMemory
A - Linear KeyboardGNU C++17Accepted0 ms1000 KB
#include <bits/stdc++.h>

using namespace std;

char s[100], a[100];
int m[100];

void solve() {
	scanf("%s%s", a, s);
	for (int i = 0; i < 26; ++i) {
		m[a[i] - 'a'] = i;		//预处理字母位置
	}
	int ans = 0, n = strlen(s);
	for (int i = 1; i < n; ++i) {
		ans += abs(m[s[i] - 'a'] - m[s[i - 1] - 'a']);	//模拟过程,代入求值
	}
	printf("%d\n", ans);
}

int main() {
#ifndef ONLINE_JUDGE
	freopen("in.txt", "r", stdin);
#endif
	int t;
	scanf("%d", &t);
	while (t--) {
		solve();
	}
	return 0;
}

B. Odd Grasshopper

题意

有一个人初始在 x 0 x_0 x0位置,每一分钟他会进行一次跳跃,其中第 i i i分钟跳跃距离为 i i i,如果在跳跃之前他的坐标是偶数,他就向左跳(坐标减 i i i),否则他向右跳(坐标加 i i i)。给出起始坐标 x 0 x_0 x0,求 n n n分钟之后他的坐标。

思路

找规律。如果从奇数点开始,则坐标的奇偶情况为 [ 奇 , 偶 , 偶 , 奇 , 奇 , …   ] [奇,偶,偶,奇,奇,\dots] [,,,,,],而从偶数点开始则为 [ 偶 , 奇 , 奇 , 偶 , 偶 , …   ] [偶,奇,奇,偶,偶,\dots] [,,,,,],不难发现每4个点为一个循环。进一步代入跳跃距离和方向,可以发现跳跃情况为 [ + 1 , − 2 , − 3 , + 4 , + 5 , …   ] [+1,-2,-3,+4,+5,\dots] [+1,2,3,+4,+5,] [ − 1 , + 2 , + 3 , − 4 , − 5 , …   ] [-1,+2,+3,-4,-5,\dots] [1,+2,+3,4,5,]。对一整个循环进行求和,发现每4步之后移动距离是0。因此,所求的时间 n n n中,前4的整数倍的时间都可以忽略,我们只需对最后的余数进行暴力模拟即可。

时间复杂度

O ( 1 ) O(1) O(1)

AC代码
ProblemLangVerdictTimeMemory
B - Odd GrasshopperGNU C++17Accepted15 ms1000 KB
#include <bits/stdc++.h>

using namespace std;

void solve() {
	long long x, n;		//数据比较大,记得开long long
	scanf("%lld%lld", &x, &n);
	for (long long i = n / 4 * 4 + 1; i <= n; ++i) {
		if (x & 1) x += i;	//奇数
		else x -= i;		//偶数
	}
	printf("%lld\n", x);
}

int main() {
#ifndef ONLINE_JUDGE
	freopen("in.txt", "r", stdin);
#endif
	int t;
	scanf("%d", &t);
	while (t--) {
		solve();
	}
	return 0;
}

C. Minimum Extraction

题意

给出一个长度为 n n n的整数数组 a a a,当数组 a a a的元素个数多于1时,允许进行以下操作:

  • 首先选定一个数组中的最小元素,如果最小元素有多个,可以任选一个,记这个元素的值为 m m m
  • 删除选中的元素,并将数组中其余所有元素减少 m m m

每经过一次操作,数组长度就会减少1。

你可以进行任意次操作(显然数量范围在 [ 0 , n − 1 ] [0,n-1] [0,n1]内),使得数组中的最小值最大化。

思路

由于答案显然和数组的顺序无关,那么我们将数组从小到大排个序,一定不会影响答案。由于操作时,整个数组剩余的元素都会减少 m m m,那么相邻元素之间的差值不会发生变化。刚才我们将数组排了序,不难发现,元素的删除顺序是从 a 1 a_1 a1 a n − 1 a_{n-1} an1,而第 i i i个元素在删除之前元素大小为 a i − a i − 1 a_i-a_{i-1} aiai1 i = 1 i=1 i=1除外),也就是数组当前状态下的的最小值。因此,答案就是,从小到大排序后, a 1 a_1 a1 a i − a i − 1 ( 1 < i ≤ n ) a_i-a_{i-1}(1<i\le n) aiai1(1<in)中最较大的值。

时间复杂度

O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)

#include <bits/stdc++.h>

using namespace std;

int a[200005];

void solve() {
	int n;
	scanf("%d", &n);
	for (int i = 0; i < n; ++i) {
		scanf("%d", &a[i]);
	}
	sort(a, a + n);		//从小到大排序
	int minm = a[0];	//求数组最小值
	for (int i = 1; i < n; ++i) {
		minm = max(minm, a[i] - a[i - 1]);	//求解
	}
	printf("%d\n", minm);
}

int main() {
#ifndef ONLINE_JUDGE
	freopen("in.txt", "r", stdin);
#endif
	int t;
	scanf("%d", &t);
	while (t--) {
		solve();
	}
	return 0;
}

D. Blue-Red Permutation

题意

给出一个长度为 n n n的整数数组 a a a,每个元素倍染成了红色或蓝色。每次操作允许将任何一个蓝色元素的值减1,或将任何一个红色元素的值加1,操作可以进行任意次。问经过若干次操作后,数组的值是否有可能恰好为 [ 1 , n ] [1,n] [1,n]中的每一个正整数(不一定要按顺序排列)。

思路

由于蓝色元素可以任意减少,红色元素可以任意增加,我们不妨令最终得到的数组中蓝色元素均小于红色元素(换句话说,将 [ 1 , n ] [1,n] [1,n]切分为两段,较小的一段均为蓝色,较大的一段均为红色)。将元素按颜色分类并排序,对于红色元素,由于较大的元素不能减小,我们应当首先处理最大的元素,并令其最终变为 n n n,然后处理次大元素,变为 n − 1 n-1 n1……对于值为 x x x的红色元素,若其目标数值为 y y y,当 x > y x>y x>y时,操作无法完成。蓝色元素相反操作即可。当所有元素都能完成操作时,整体目标才能达成

时间复杂度

O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)

AC代码
ProblemLangVerdictTimeMemory
D - Blue-Red PermutationGNU C++17Accepted78 ms3600 KB
#include <bits/stdc++.h>

using namespace std;

int a[200005];
char s[200005];

void solve() {
	int n;
	scanf("%d", &n);
	for (int i = 0; i < n; ++i) {
		scanf("%d", &a[i]);
	}
	scanf("%s", s);
	vector<int> u, v;
	for (int i = 0; i < n; ++i) {
		if (s[i] == 'B') u.push_back(a[i]);	//u存储蓝色元素
		else v.push_back(a[i]);				//v存储红色元素
	}
	sort(u.begin(), u.end());					//从小到大排
	sort(v.begin(), v.end(), greater<int>());	//从大到小排
	for (int i = 0; i < u.size(); ++i) {
		if (u[i] < i + 1) {		//模拟过程
			puts("NO");
			return;
		}
	}
	for (int i = 0; i < v.size(); ++i) {
		if (v[i] > n - i) {		//模拟过程
			puts("NO");
			return;
		}
	}
	puts("YES");
}

int main() {
#ifndef ONLINE_JUDGE
	freopen("in.txt", "r", stdin);
#endif
	int t;
	scanf("%d", &t);
	while (t--) {
		solve();
	}
	return 0;
}

E. Robot on the Board 1

题意

现有一个 n × m n\times m n×m的长方形区域( n n n m m m列),有一个机器人在该区域内。现给机器人一系列指令,使之从当前格子向上下左右中的某个格子移动。如果指令会使得机器人移出区域,则该指令不执行。要求指定机器人的起始位置,使得执行的指令数尽可能大。

思路

本题要让操作数最大,我们不妨贪心地让能够执行的指令尽量执行。由于出发的位置并不确定,我们设起始位置为 ( 0 , 0 ) (0,0) (0,0),在模拟机器人执行指令的过程中,记录当前位置和四个方向的位移极值(详见代码)。因为区域大小有限制,左右方向极值之差不能超过列数,上下方向极值只差不能超过行数,如果下一条指令将使得超界,则无法执行这条指令。模拟完成后按照边界反推原点位置,计算出发点位置即可。

时间复杂度

O ( n ) ( 此 处 n 指 的 是 指 令 的 个 数 ) O(n)(此处n指的是指令的个数) O(n)(n)

AC代码
ProblemLangVerdictTimeMemory
E - Robot on the Board 1GNU C++17Accepted30 ms2000 KB
#include <bits/stdc++.h>

using namespace std;

char s[1000005];

void solve() {
	int n, m;
	scanf("%d%d", &n, &m);
	scanf("%s", s);		//操作序列
	int x = 0, y = 0, x1 = 0, x2 = 0, y1 = 0, y2 = 0;	//x1上极限,x2下极限,y1左极限,y2右极限
	int q = strlen(s), ans = 0;
	for (int i = 0; i < q; ++i) {	//模拟
		switch (s[i]) {
			case 'L':
				if (y > y1) {	//在已到达的范围内,无需扩充范围,下同
					++ans;
					--y;
				} else if (y2 - y1 + 1 < m) {	//超过已到达的范围,需要扩大边界,下同
					++ans;
					--y;
					--y1;
				}
				break;
			case 'R':
				if (y < y2) {
					++ans;
					++y;
				} else if (y2 - y1 + 1 < m) {
					++ans;
					++y;
					++y2;
				}
				break;
			case 'U':
				if (x > x1) {
					++ans;
					--x;
				} else if (x2 - x1 + 1 < n) {
					++ans;
					--x;
					--x1;
				}
				break;
			case 'D':
				if (x < x2) {
					++ans;
					++x;
				} else if (x2 - x1 + 1 < n) {
					++ans;
					++x;
					++x2;
				}
				break;
		}
	}
	printf("%d %d\n", 1 - x1, 1 - y1);	//反推得到出发点相对原点的坐标
}

int main() {
#ifndef ONLINE_JUDGE
	freopen("in.txt", "r", stdin);
#endif
	int t;
	scanf("%d", &t);
	while (t--) {
		solve();
	}
	return 0;
}

F. Robot on the Board 2

题意

现有一个 n × m n\times m n×m的长方形区域( n n n m m m列),有一个机器人在该区域内。现给每个位置指定一个指令,使之从当前格子向上下左右中的某个格子移动。机器人会不断执行指令直到移出区域或进入已经走过的路径上的任何一个位置。要求指定机器人的起始位置,使得执行的指令数尽可能大。

思路

因为超出边界或者后立刻停止一切指令,所以我们只要处理每个点的路径长度即可。求路径长度采用记忆化dfs的方法,在dfs的过程中需要记录dfs序,以便于找环。对点 i i i,其路径长度的转移方程如下:
a n s i = { a n s n x t i + 1 i 不 在 环 上 a n s n x t i i 在 环 上 ans_i=\left\{\begin{array}{ll} ans_{nxt_i}+1&i不在环上\\ ans_{nxt_i}&i在环上\end{array}\right. ansi={ansnxti+1ansnxtiii
其中, n x t i nxt_i nxti表示 i i i的下一步进入的点。特别地,超出边界的点, a n s = 0 ans=0 ans=0。当找到一个环的时候,环的大小就是该点能执行的指令数。

本题的思路并不难,难点在于代码实现,我认为我的代码并不完美,仅作参考,

时间复杂度

O ( n m ) O(nm) O(nm)

AC代码
ProblemLangVerdictTimeMemory
F - Robot on the Board 2GNU C++17Accepted265 ms227900 KB
#include <bits/stdc++.h>

using namespace std;
const int N = 2005;
char mp[N][N];
bool cir[N][N];
int vis[N][N];
int ans[N][N];
int n, m;

int dfs(int p, int x, int y, bool &c) {		//c表示上一个点是否在环上
	if (x < 0 || x >= n || y < 0 || y >= m) {	//超出边界了,返回0
		c = false;
		return 0;
	}
	if (vis[x][y]) {
		if (ans[x][y] == -1) {		//还未产生答案,但是却已经dfs过,说明恰好成环了
			c = true;
			cir[x][y] = true;	//记录该点在环上,在后面有用
			return p - vis[x][y];	//记录的dfs序在这里起作用(求出环的大小)
		} else {
			c = false;
			return ans[x][y];		//已经搜过的点,直接返回答案
		}
	}
	vis[x][y] = p;
	int res;
	bool nc;	//下一个点是否在环上
	if (mp[x][y] == 'U') res = dfs(p + 1, x - 1, y, nc);
	if (mp[x][y] == 'D') res = dfs(p + 1, x + 1, y, nc);
	if (mp[x][y] == 'L') res = dfs(p + 1, x, y - 1, nc);
	if (mp[x][y] == 'R') res = dfs(p + 1, x, y + 1, nc);
	if (cir[x][y]) c = false;	//之前记录了该点在环上,说明是环的起点,上一个点不在环上
	else cir[x][y] = c = nc;
	if (!nc) ++res;		//不在环上答案要+1
	return ans[x][y] = res;
}

void solve() {
	scanf("%d%d", &n, &m);
	for (int i = 0; i < n; ++i) {
		for (int j = 0; j < m; ++j) {	//赋初值不要全都memset,很容易超时
			ans[i][j] = -1;
			vis[i][j] = 0;
			cir[i][j] = false;
		}
	}
	for (int i = 0; i < n; ++i) scanf("%s", mp[i]);
	int x, y, r = 0;	//x和y记录坐标,r记录最大结果
	for (int i = 0; i < n; ++i) {
		for (int j = 0; j < m; ++j) {
			bool c;	//dfs要传入引用,因此需要一个变量来占位,没有实际用途
			int cur = dfs(1, i, j, c);
			if (cur > r) {
				x = i + 1;	//我的下标从0开始,所以答案要+1,下同
				y = j + 1;
				r = cur;
			}
		}
	}
	printf("%d %d %d\n", x, y, r);
}

int main() {
#ifndef ONLINE_JUDGE
	freopen("in.txt", "r", stdin);
#endif
	int t;
	scanf("%d", &t);
	while (t--) {
		solve();
	}
	return 0;
}

G. Banquet Preparations 1

题意

现有 n n n份菜,第 i i i份菜由 a i a_i ai克鱼和 b i b_i bi份肉组成。要求在每一份菜中都吃掉恰好 m m m克(鱼和肉的占比不限),使得最终剩下的菜中,鱼的总重量和肉的总重量最接近,输出最优情况下两者差的绝对值,并输出任何一种吃的方案。

思路

阅读理解题,题意很难懂,读懂题意再动手。

我们记所有的鱼加起来的重量为 A = ∑ i = 1 n a i A=\sum_{i=1}^{n}a_i A=i=1nai,所有的肉加起来的重量为 B = ∑ i = 1 n b i B=\sum_{i=1}^{n}b_i B=i=1nbi,则吃完后剩余食物总重量 s = A + B − n × m s=A+B-n\times m s=A+Bn×m。那么,为了让鱼和肉的剩余重量之差最小化,我们希望两者最终剩余的重量和分别为 ⌊ s 2 ⌋ \lfloor\frac{s}{2}\rfloor 2s ⌈ s 2 ⌉ \lceil\frac{s}{2}\rceil 2s

接下来我们计算最多能留下多少鱼和肉(这与上述的 A A A B B B不同),当某份菜中,鱼(或肉)的重量少于 m m m克时,我们不得不吃一些肉(或鱼)来凑足 m m m克,否则我们可以留下所有的肉(或鱼)。

记最大剩下的鱼重量为 s a s_a sa,最大留下的肉的重量为 s b s_b sb,若 s a < ⌊ s 2 ⌋ s_a<\lfloor\frac{s}{2}\rfloor sa<2s,则我们应当尽可能的保留鱼(反之 s b < ⌊ s 2 ⌋ s_b<\lfloor\frac{s}{2}\rfloor sb<2s时应尽可能的保留肉),当 s a , s b ≥ ⌊ s 2 ⌋ s_a,s_b\ge\lfloor\frac{s}{2}\rfloor sa,sb2s时,我们只需构造出一种方案,使得剩下的鱼的总重量恰好为 ⌊ s 2 ⌋ \lfloor\frac{s}{2}\rfloor 2s即可。

关于构造方案,不难发现,有一部分菜中,我们一定会剩下一部分鱼(或肉),因为其重量超过了 m m m,因此这部分重量必须先算上。随后我们采取贪心策略,尽可能多的留下鱼,直到留下的重量达到我们预期的 ⌊ s 2 ⌋ \lfloor\frac{s}{2}\rfloor 2s

时间复杂度

O ( n ) O(n) O(n)

AC代码
ProblemLangVerdictTimeMemory
G - Banquet Preparations 1GNU C++17Accepted139 ms2600 KB
#include <bits/stdc++.h>

using namespace std;
const int N = 2e5 + 5;
int a[N], b[N];

void solve() {
	int n, m;
	scanf("%d%d", &n, &m);
	long long sum = 0, sa = 0, sb = 0;
	for (int i = 0; i < n; ++i) {
		scanf("%d%d", &a[i], &b[i]);
		sum += a[i] + b[i];
	}
	long long md = (sum - 1ll * n * m) / 2;		//求出预期最接近的重量
	for (int i = 0; i < n; ++i) {
		if (b[i] < m) sa += a[i] + b[i] - m;	//求最多留下多少鱼
		else sa += a[i];
		if (a[i] < m) sb += a[i] + b[i] - m;	//求最多留下多少肉
		else sb += b[i];
	}
	if (sa < md) {					//鱼不足
		printf("%lld\n", sum - 1ll * n * m - sa - sa);
		for (int i = 0; i < n; ++i) {
			if (b[i] < m) printf("%d %d\n", m - b[i], b[i]);
			else printf("%d %d\n", 0, m);
		}
	} else if (sb < md) {			//肉不足
		printf("%lld\n", sum - 1ll * n * m - sb - sb);
		for (int i = 0; i < n; ++i) {
			if (a[i] < m) printf("%d %d\n", a[i], m - a[i]);
			else printf("%d %d\n", m, 0);
		}
	} else {						//可以达到预期最接近的重量
		printf("%lld\n", sum - 1ll * n * m & 1);
		sa = md, sb = sum - 1ll * n * m - md;
		for (int i = 0; i < n; ++i) {
			int ma = max(0, a[i] - m), mb = max(0, b[i] - m);
			sa -= ma, sb -= mb;		//预先扣除必须剩下的重量
		}
		for (int i = 0; i < n; ++i) {
			int ma = max(0, a[i] - m), mb = max(0, b[i] - m);
			a[i] -= ma, b[i] -= mb;
			int da = max(1ll * m - b[i], a[i] - min(1ll * a[i], sa)), db = m - da;
			printf("%d %d\n", da, db);	//da是吃了多少鱼,db是吃了多少肉
			sa -= a[i] - da, sb -= b[i] - db;
		}
	}
}

int main() {
#ifndef ONLINE_JUDGE
	freopen("in.txt", "r", stdin);
#endif
	int t;
	scanf("%d", &t);
	while (t--) {
		solve();
	}
	return 0;
}

H. Banquet Preparations 2

题意

现有 n n n份菜,第 i i i份菜由 a i a_i ai克鱼和 b i b_i bi份肉组成。要求在每一份菜中都吃掉恰好 m i m_i mi克(鱼和肉的占比不限),使得最终剩下的菜中,不同的菜的数量尽可能地小,认为两份菜相同当且仅当菜中鱼的重量和肉的重量都分别相等( a x = a y ∧ b x = b y a_x=a_y\wedge b_x=b_y ax=aybx=by),输出最优情况下不同菜品的数量,并输出任何一种吃的方案。

思路

又是阅读理解题,题意非常难懂,读懂题意再动手。

思考:怎样的两个菜,在吃完以后可能会变成相同的菜?

思考之后可以发现,只有 a i + b i − m i a_i+b_i-m_i ai+bimi相等的菜最终才有可能相同(必要条件)。我们不妨把菜按这个值的大小进行分类。

在每一组中,由于最终状态下的菜总重相等,如果鱼的重量确定,则肉的重量也确定了。因此我们只关心剩下了多少鱼。我们需要求出每一份菜在最后可能剩下多少鱼,这一定是一个连续的区间,处理方法和上一题有相似之处,在此不作详述。

至此,问题转化为:给定若干个区间,将它们分成若干个互不相交的集合(准确来说是构造全集的一个划分),使得每个集合内区间的交集非空。

考虑贪心策略。首先将区间按左端点大小从小到大排序。由于左端点最小的区间必须属于一个集合,我们以这个区间作为集合的首元素创建集合。同时,我们需要维护集合内区间的交集(实际上只需维护交集的右端点,因为左端点是非递减的)。根据贪心策略,我们尝试将下一个区间加入集合,如果成功加入当前集合,得到的答案一定不会更劣。判断区间可以加入集合的依据是区间的左端点小于等于当前交集的右端点(这样加入后区间交集才非空),然后更新交集右端点。重复上述过程,直到下一个区间不能再加入集合。此时的区间交集上任意一点都可以作为最终状态下这份菜剩下鱼的重量(不妨取区间右端点,换其他的也行),然后记录构造方案,接着以下一个区间为首元素创建新的集合,重复上述过程,直到处理了全部集合。注意,此时我们还没记录最后一个集合的构造方案,我们需要补上这一过程。

全部处理完所有可能的末状态之后,按顺序输出即可。本题中,单个文件测试数据比较多,不要每次都枚举0到2000000,效率太低了,之前在分类时用set记录有效的位置,此时枚举有效位置即可。

时间复杂度

O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)

AC代码
ProblemLangVerdictTimeMemory
H - Banquet Preparations 2GNU C++17Accepted296 ms36800 KB
#include <bits/stdc++.h>

using namespace std;

struct dt {		//记录菜的信息
	int a, b, m, aa, bb;	//aa是吃多少鱼,bb是吃多少肉
} d[200005];

struct seg {	//记录区间信息
	int l, r, id;	//记录id是为了记录方案
};

vector<seg> v[2000005];		//数组要开2e5,因为a和b都是1e5,加起来是会达到2e5的
set<int> s;

void solve() {
	int n;
	scanf("%d", &n);
	for (int i = 0; i < n; ++i) {
		scanf("%d%d%d", &d[i].a, &d[i].b, &d[i].m);
		v[d[i].a + d[i].b - d[i].m].push_back(seg{d[i].m > d[i].a ? 0 : d[i].a - d[i].m, d[i].m > d[i].b ? d[i].a + d[i].b - d[i].m : d[i].a, i});		//处理可行区间,分类
		s.insert(d[i].a + d[i].b - d[i].m);
	}
	int ans = 0;
	for (int i: s) {	//遍历s中元素,依次处理
		sort(v[i].begin(), v[i].end(), [&](seg &x, seg &y) { return x.l < y.l; });	//对区间按左端点从小到大排序,用了lambda表达式,不会的可以写个cmp函数替代
		int l = -1, r = -1;		//这里l和r的含义不同,l记录首元素下标,r记录交集右端点,初始化为-1一定与第一个区间没有交集,下面的for循环中首次循环会进入if语句段
		for (int j = 0; j < v[i].size(); ++j) {
			if (v[i][j].l > r) {
				if (l != -1) {
					for (int k = l; k < j; ++k) {	//方案构造
						d[v[i][k].id].aa = d[v[i][k].id].a - r;
						d[v[i][k].id].bb = d[v[i][k].id].m - d[v[i][k].id].aa;
					}
				}
				++ans;	//创建新集合
				l = j;
				r = v[i][j].r;
			} else {
				r = min(r, v[i][j].r);
			}
		}
		if (l != -1) {	//完成后还需补上最后一组的方案构造
			for (int k = l; k < v[i].size(); ++k) {
				d[v[i][k].id].aa = d[v[i][k].id].a - r;
				d[v[i][k].id].bb = d[v[i][k].id].m - d[v[i][k].id].aa;
			}
		}
		v[i].clear();	//顺便清空了,此后用不到了
	}
	printf("%d\n", ans);
	for (int i = 0; i < n; ++i) {
		printf("%d %d\n", d[i].aa, d[i].bb);
	}
}

int main() {
#ifndef ONLINE_JUDGE
	freopen("in.txt", "r", stdin);
#endif
	int t;
	scanf("%d", &t);
	while (t--) {
		solve();
	}
	return 0;
}

后记

可恶,F题我表演了大炮打蚊子,1个dfs就能解决的问题,我写了强连通分量,3个dfs,不仅麻烦还成功的MLE了,从rated rk13掉到rated rk71,这波血亏。。。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值