【AtCoder】AtCoder Grand Contest 037 题解

【比赛链接】

  • 点击打开链接

【题解链接】

  • 点击打开链接

【A】 Dividing a String

【思路要点】

  • 设一个划分中第一次出现长度超过 2 2 2 S i S_i Si 的位置为 i i i
  • 则可以通过将 S i S_i Si 划分为更小的串,以及将 S i S_i Si 的某一个后缀拼接至 S i + 1 S_{i+1} Si+1 的方式使得第一次出现长度超过 2 2 2 S i S_i Si 的位置大于 i i i
  • 从而可以证明至少存在一个最优解使得划分中的字符串长度均不超过 2 2 2
  • 直接 d p dp dp 即可,时间复杂度 O ( ∣ S ∣ ) O(|S|) O(S)

【代码】

#include
using namespace std;
const int MAXN = 2e5 + 5;
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("");
}
int dp[MAXN][2];
char s[MAXN];
int main() {
	scanf("%s", s + 1);
	int n = strlen(s + 1);
	for (int i = 0; i <= n + 1; i++)
		dp[i][0] = dp[i][1] = -1e8;
	dp[0][0] = 0, dp[1][0] = 1;
	for (int i = 2; i <= n; i++) {
		chkmax(dp[i][0], dp[i - 1][1] + 1);
		if (s[i] != s[i - 1]) chkmax(dp[i][0], dp[i - 1][0] + 1);
		chkmax(dp[i][1], dp[i - 2][0] + 1);
		if (s[i] != s[i - 2] || s[i - 1] != s[i - 3]) chkmax(dp[i][1], dp[i - 2][1] + 1);
	}
	writeln(max(dp[n][0], dp[n][1]));
	return 0;
}

【B】 RGB Balls

【思路要点】

  • 首先,答案一定会乘上 N ! N! N!
  • ∑ i ( c i − a i ) = ∑ i c i − ∑ i a i \sum_{i}(c_i-a_i)=\sum_{i}c_i-\sum_ia_i i(ciai)=iciiai ,因此,最小化 ∑ i ( c i − a i ) \sum_{i}(c_i-a_i) i(ciai) 要求我们在 ∑ i c i \sum_ic_i ici 不变的前提下最大化 ∑ i a i \sum_ia_i iai ,或者在 ∑ i a i \sum_ia_i iai 不变的前提下最小化 ∑ i c i \sum_ic_i ici
  • 考虑一个从前向后匹配的过程,不失一般性地,假设现在正要匹配一个 R R R
  • 若存在一个 G B GB GB B G BG BG ,一定只有将 R R R 加在它们的后面一定是最优的,这是因为如果加在 G , B G,B G,B 的后面,就会导致在 ∑ i a i \sum_ia_i iai 不变的前提下 ∑ i c i \sum_ic_i ici 变得更大。
  • 否则,若存在 G G G B B B ,一定只有将 R R R 加在它们的后面一定是最优的,这是因为如果加在空串的后面,就会导致在 ∑ i c i \sum_ic_i ici 不变的前提下 ∑ i a i \sum_ia_i iai 变得更小。
  • 否则,只有将 R R R 加在空串的后面一种选择。
  • 从上面的过程可以看出,同一时刻不可能同时存在 R , G , B R,G,B R,G,B 中的两种。
  • 因此,开若干个变量记录 B , G , R B,G,R B,G,R R B RB RB B R BR BR B G BG BG G B GB GB R G RG RG G R GR GR 的个数,每次匹配时将答案乘上对应的个数即可。
  • 时间复杂度 O ( N ) O(N) O(N)

【代码】

#include
using namespace std;
const int MAXN = 3e5 + 5;
const int P = 998244353;
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("");
}
char s[MAXN];
int main() {
	int n, ans = 1; read(n);
	scanf("%s", s + 1);
	for (int i = 1; i <= n; i++)
		ans = 1ll * ans * i % P;
	int r = 0, g = 0, b = 0, rg = 0, rb = 0, gb = 0;
	for (int i = 1; i <= 3 * n; i++) {
		if (s[i] == 'R') {
			if (gb) {
				ans = 1ll * ans * gb % P;
				gb--;
			} else if (g) {
				ans = 1ll * ans * g % P;
				g--; rg++;
			} else if (b) {
				ans = 1ll * ans * b % P;
				b--; rb++;
			} else r++;
		}
		if (s[i] == 'B') {
			if (rg) {
				ans = 1ll * ans * rg % P;
				rg--;
			} else if (g) {
				ans = 1ll * ans * g % P;
				g--; gb++;
			} else if (r) {
				ans = 1ll * ans * r % P;
				r--; rb++;
			} else b++;
		}
		if (s[i] == 'G') {
			if (rb) {
				ans = 1ll * ans * rb % P;
				rb--;
			} else if (b) {
				ans = 1ll * ans * b % P;
				b--; gb++;
			} else if (r) {
				ans = 1ll * ans * r % P;
				r--; rg++;
			} else g++;
		}
	}
	writeln(ans);
	return 0;
}

【C】 Numbers on a Circle

【思路要点】

  • 考虑将操作倒序,即一次操作让一个数减去旁边两个数的和。
  • 不难发现,这样的操作只能对大于两侧的数 x , z x,z x,z 的数 y y y 进行,并且,直到 x , z x,z x,z 中出现一个数比 y y y 大之前, x , z x,z x,z 均不可能被操作。而 x , z x,z x,z 中出现一个数比 y y y 大的情况只有可能是对 y y y 一直进行操作,直到 y y y 变为 y % ( x + z ) y\%(x+z) y%(x+z)
  • 因此,用堆维护序列,每次找到最大值进行操作,直到不能继续操作,判断一些边界无解情况即可。
  • 时间复杂度 O ( N L o g V L o g N ) O(NLogVLogN) O(NLogVLogN)

【代码】

#include
using namespace std;
const int MAXN = 2e5 + 5;
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("");
}
int a[MAXN], b[MAXN];
priority_queue <pair <int, int>> Heap;
int main() {
	int n; read(n); ll ans = 0;
	for (int i = 1; i <= n; i++)
		read(b[i]);
	for (int i = 1; i <= n; i++)
		read(a[i]), Heap.push(make_pair(a[i], i));
	while (!Heap.empty()) {
		pair <int, int> tmp = Heap.top(); Heap.pop();
		if (b[tmp.second] == tmp.first) continue;
		if (b[tmp.second] > tmp.first) {
			writeln(-1);
			return 0;
		}
		int dis = tmp.first - b[tmp.second];
		int sum = a[tmp.second - 1] + a[tmp.second + 1];
		if (tmp.second == 1) sum += a[n];
		if (tmp.second == n) sum += a[1];
		if (sum > dis) {
			writeln(-1);
			return 0;
		}
		int add = dis / sum; ans += add;
		a[tmp.second] -= add * sum;
		tmp.first = a[tmp.second];
		Heap.push(tmp);
	}
	writeln(ans);
	return 0;
}

【D】 Sorting a Grid

【思路要点】

  • 输入的每一行的顺序是无关的,因此,可以将输入描述为 N × M N\times M N×M 个二元组 ( x i , y i ) (x_i,y_i) (xi,yi) ,即 i i i S , T S,T S,T 中出现的行号 x i , y i x_i,y_i xi,yi
  • 我们需要将 i i i 分配在 B B B 的第 x i x_i xi 行, C C C 的第 y i y_i yi 行,并且使它们在同一列。
  • 考虑构建如下二分图,对于每一个 i i i ,连接左侧第 x i x_i xi 个点和右侧第 y i y_i yi 个点,则左侧与右侧每一个点的度数均为 M M M ,由 H a l l Hall Hall 定理,该图存在完美匹配。
  • 因此,找到一个匹配用于填第一列,删掉对应的边,重复该过程即可。
  • 时间复杂度 O ( N 3 N ) O(N^3\sqrt{N}) O(N3N ) ,其中 N , M N,M N,M 同阶。

【代码】

#include
using namespace std;
const int MAXN = 105;
const int MAXM = 1e4 + 5;
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("");
}
int n, m, s, res[MAXN];
bool mat[MAXN][MAXN];
namespace NetworkFlow {
	const int INF = 2e9;
	const int MAXP = 205;
	struct edge {
		int dest, flow;
		unsigned pos;
	};
	vector <edge> a[MAXP];
	int tot, s, t, dist[MAXP];
	unsigned curr[MAXP];
	void addedge(int x, int y, int z) {
		a[x].push_back((edge) {y, z, a[y].size()});
		a[y].push_back((edge) {x, 0, a[x].size() - 1});
	}
	void init() {
		for (int i = 1; i <= n; i++) {
			a[i].clear();
			a[i + n].clear();
		}
		a[s = 2 * n + 1].clear();
		a[t = 2 * n + 2].clear();
		for (int i = 1; i <= n; i++) {
			addedge(s, i, 1);
			addedge(i + n, t, 1);
		} 
		for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
			if (mat[i][j]) addedge(i, j + n, 1);
	}
	int dinic(int pos, int limit) {
		if (pos == t) return limit;
		int used = 0, tmp;
		for (unsigned &i = curr[pos]; i < a[pos].size(); i++)
			if (a[pos][i].flow != 0 && dist[pos] + 1 == dist[a[pos][i].dest] && (tmp = dinic(a[pos][i].dest, min(limit - used, a[pos][i].flow)))) {
				used += tmp;
				a[pos][i].flow -= tmp;
				a[a[pos][i].dest][a[pos][i].pos].flow += tmp;
				if (used == limit) return used;
			}
		return used;
	}
	bool bfs() {
		static int q[MAXP];
		int l = 0, r = 0;
		memset(dist, 0, sizeof(dist));
		dist[s] = 1, q[0] = s;
		while (l <= r) {
			int tmp = q[l];
			for (unsigned i = 0; i < a[tmp].size(); i++)
				if (dist[a[tmp][i].dest] == 0 && a[tmp][i].flow != 0) {
					q[++r] = a[tmp][i].dest;
					dist[q[r]] = dist[tmp] + 1;
				}
			l++;
		}
		return dist[t] != 0;
	}
	void flow() {
		int ans = 0;
		while (bfs()) {
			memset(curr, 0, sizeof(curr));
			ans += dinic(s, INF);
		}
		assert(ans == n);
		for (int i = 1; i <= n; i++) {
			for (auto x : a[i]) {
				if (x.dest != s && x.flow == 0) res[i] = x.dest - n;
			}
		}
	}
}
int a[MAXN][MAXN], b[MAXN][MAXN];
int f[MAXN][MAXN], g[MAXN][MAXN];
pair <int, int> home[MAXM];
vector <int> e[MAXN][MAXN]; 
int main() {
	read(n), read(m);
	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= m; j++) {
		read(a[i][j]);
		home[a[i][j]].first = i;
		b[i][j] = ++s;
		home[b[i][j]].second = i;
	}
	for (int i = 1; i <= n * m; i++)
		e[home[i].first][home[i].second].push_back(i);
	for (int k = 1; k <= m; k++) {
		for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
			mat[i][j] = e[i][j].size() != 0;
		NetworkFlow :: init();
		NetworkFlow :: flow();
		for (int i = 1; i <= n; i++) {
			int tmp = e[i][res[i]].back();
			e[i][res[i]].pop_back();
			f[i][k] = tmp;
			g[res[i]][k] = tmp;
		}
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++)
			printf("%d ", f[i][j]);
		printf("\n");
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++)
			printf("%d ", g[i][j]);
		printf("\n");
	}
	return 0;
}

【E】 Reversing and Concatenating

【思路要点】

  • 令字符串中出现的最小的字符为 M i n Min Min ,考虑答案串开头 M i n Min Min 的个数 L e n Len Len
  • 若字符串最后一个字符为 M i n Min Min ,连续长度为 L L L ,则 L e n Len Len 应当与 L × 2 K L\times2^{K} L×2K 取最大值。
  • 令字符串中间最长的一段字符 M i n Min Min 长度为 L L L ,则 L e n Len Len 应当与 L × 2 K − 1 L\times2^{K-1} L×2K1 取最大值。
  • 使得 L e n Len Len 取到最大值的方式只有每一次操作使得末尾处的 M i n Min Min 的长度翻倍。
  • 在所有可能使得 L e n Len Len 取到最大值的方式中取答案字典序最小的一种即可。
  • 时间复杂度 O ( N 2 ) O(N^2) O(N2)

【代码】

#include
using namespace std;
const int MAXN = 1e4 + 5;
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("");
}
int n, k;
char Min, Max, ans[MAXN], s[MAXN];
void check(char *s, int k) {
	static char t[MAXN];
	int now = n, len = 0;
	while (now >= 1 && s[now] == Min) now--, len++;
	assert(len != 0);
	while (k != 0 && len < n) len *= 2, k--;
	if (len >= n) {
		for (int i = 1; i <= n; i++)
			ans[i] = Min;
		printf("%s\n", ans + 1);
		exit(0);
	}
	for (int i = 1; i <= len; i++)
		t[i] = Min;
	for (int i = len + 1; i <= n; i++)
		t[i] = s[now--];
	for (int i = 1; i <= n; i++)
		if (t[i] < ans[i]) {
			for (int j = 1; j <= n; j++)
				ans[j] = t[j];
			return;
		} else if (t[i] > ans[i]) return;
}
int main() {
	read(n), read(k);
	scanf("%s", s + 1);
	Min = 'z', Max = 'a';
	for (int i = 1; i <= n; i++) {
		chkmin(Min, s[i]);
		chkmax(Max, s[i]);
		s[2 * n - i + 1] = s[i];
		ans[i] = 'z';
	}
	if (Min == Max) {
		printf("%s\n", s + 1);
		return 0;
	}
	if (s[n] == Min) check(s, k);
	for (int i = n; i <= 2 * n; i++)
		if (s[i] == Min) check(s + i - n, k - 1);
	printf("%s\n", ans + 1);
	return 0;
}

【F】 Counting of Subarrays

【思路要点】

  • 考虑如何判定一个序列合法。
  • 首先,若序列长度为 1 1 1 ,序列合法。
  • 若序列仅包含一种元素,当且仅当序列长度 ≥ L \geq L L ,序列合法。
  • 否则,取一段极长的仅包含最小值 M i n Min Min 的区间 [ l i , r i ] [l_i,r_i] [li,ri] ,则要求 r i − l i + 1 ≥ L r_i-l_i+1\geq L rili+1L ,并且以 ⌊ r i − l i + 1 L ⌋ \lfloor\frac{r_i-l_i+1}{L}\rfloor Lrili+1 M i n + 1 Min+1 Min+1 代替之得到的序列合法。
  • 将问题拓展一下,为每一个位置指定两个数 X i , Y i X_i,Y_i Xi,Yi ,对于所有合法区间 [ l , r ] [l,r] [l,r] ,求 X l × Y r X_l\times Y_r Xl×Yr 的和。
  • 考虑上面的判断合法的过程,实际上我们可以将每一个区间放在一起考虑
  • 即每次处理序列中现存的最小值 M i n Min Min 所在的位置,并计算新产生的 M i n + 1 Min+1 Min+1 对应的 X i , Y i X_i,Y_i Xi,Yi
  • 考虑一段最小值 M i n Min Min 产生的 M i n + 1 Min+1 Min+1 对应的 X i , Y i X_i,Y_i Xi,Yi ,第 1 , 2 , … , L − 1 1,2,\dots,L-1 1,2,,L1 个元素显然不能作为区间的右端点,不对 Y i Y_i Yi 产生贡献; L , L + 1 , … , 2 L − 1 L,L+1,\dots,2L-1 L,L+1,,2L1 个元素作为区间的右端点可以产生一个 M i n + 1 Min+1 Min+1 ,对 Y 1 Y_1 Y1 产生贡献; 2 L , 2 L + 1 , … , 3 L − 1 2L,2L+1,\dots,3L-1 2L,2L+1,,3L1 个元素作为区间的右端点可以产生两个 M i n + 1 Min+1 Min+1 ,对 Y 2 Y_2 Y2 产生贡献,以此类推, X i X_i Xi 同理。
  • 具体细节建议参考代码,时间复杂度 O ( N L o g N ) O(NLogN) O(NLogN)

【代码】

#include
using namespace std;
const int MAXN = 2e5 + 5;
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("");
}
int n, l, x[MAXN];
pair <int, int> y[MAXN];
ll calc(vector <pair <int, int>> &a) {
	ll ans = 0, sum = 0;
	for (unsigned i = l - 1, j = 0; i < a.size(); i++, j++) {
		sum += a[j].first;
		ans += a[i].second * sum;
	}
	return ans;
}
int main() {
	read(n), read(l);
	for (int i = 1; i <= n; i++) {
		read(x[i]);
		y[i] = make_pair(x[i], i);
	}
	sort(y + 1, y + n + 1); ll ans = n; int val = 0, pos = 1;
	vector <pair <pair <int, int>, pair <int, int>>> a, b;
	while (true) {
		if (a.empty()) {
			if (pos > n) break;
			else val = y[pos].first;
		} else val++;
		while (val == y[pos].first) a.emplace_back(make_pair(y[pos].second, y[pos].second), make_pair(1, 1)), pos++;
		b.clear(), sort(a.begin(), a.end());
		for (unsigned i = 0, j; i < a.size(); i = j + 1) {
			j = i; while (j + 1 < a.size() && a[j + 1].first.first == a[j].first.second + 1) j++;
			int len = j - i + 1, cnt = len / l;
			if (cnt != 0) {
				vector <pair <int, int>> cur, where, value;
				for (unsigned k = i; k <= j; k++)
					cur.push_back(a[k].second);
				ans += calc(cur);
				for (int k = 1; k <= cnt; k++) {
					where.emplace_back(a[i].first.first + k - 1, (k == cnt) ? a[j].first.second : a[i].first.first + k - 1);
					value.emplace_back(0, 0);
				}
				for (unsigned k = i; k <= j; k++) {
					int tl = k - i + 1, tr = j - k + 1;
					if (tl >= l) value[tl / l - 1].second += a[k].second.second;
					if (tr >= l) value[cnt - tr / l].first += a[k].second.first;
				}
				ans -= calc(value);
				for (unsigned k = 0; k < where.size(); k++)
					b.emplace_back(where[k], value[k]);
			} 
		}
		a = b;
	}
	writeln(ans);
	return 0;
}

你可能感兴趣的:(【类型】做题记录,【OJ】AtCoder,【算法】动态规划,【算法】贪心,【数据结构】堆,【算法】线性规划与网络流,【算法】最大流,【算法】Hall定理,【算法】构造与证明,【资料】好题)