AtCoder Grand Contest 037 简要题解

Posted yyf0309

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AtCoder Grand Contest 037 简要题解相关的知识,希望对你有一定的参考价值。

从这里开始

Problem A Dividing a String

  猜想每段长度不超过2。然后dp即可。

  考虑最后一个长度大于等于3的一段,如果划成$1 + 2$会和后面相同,那么划成$2 + 1$,如果前一段和前面相同,那么把前一段和前面合并。每次操作后段数都不会减少。所以存在一种最优方案使得每段长度不超过2。

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

const int N = 2e5 + 5;

template <typename T>
void vmax(T& a, T b) {
	(a < b) && (a = b, 0);
}

int n;
char s[N];
int f[N][2];

int main() {
	scanf("%s", s + 1);
	f[0][0] = 0, f[0][1] = -1e9;
	n = strlen(s + 1);
	for (int i = 1; i <= n; i++) {
		f[i][0] = f[i][1] = -1e9;
		vmax(f[i][0], f[i - 1][1] + 1);
		if (s[i] != s[i - 1])
			vmax(f[i][0], f[i - 1][0] + 1);
		if (i > 1) {
			vmax(f[i][1], f[i - 2][0] + 1);
			if (i > 3 && (s[i] != s[i - 2] || s[i - 1] != s[i - 3])) {
				vmax(f[i][1], f[i - 2][1] + 1);
			}
		}
	}
	printf("%d
", max(f[n][0], f[n][1]));
	return 0;
}

Problem B RGB Balls

  假设红绿蓝三种颜色的求按顺序排列后分别为 $r_1, r_2, cdots, r_n, g_1, g_2, cdots, g_n, b_1, b_2, cdots, b_n$。

  设$m_i = min{r_i, b_i, g_i}, M_i = max{r_i, b_i, g_i}$。猜想答案是$sum M_i - m_i$。

  假设每个人拿到的最小标号的球标号递增,证明考虑前$k$个人至多选择前$k$个红球,白球和蓝球,所以第$k + 1$个人拿到的最小标号的球的标号至少为$m_{k + 1}$,所以$m_i$是第$i$个人拿到的最小标号的球的上界。同理可以证明第$i$个人拿到的最大标号的球的下界是$M_i$。

  然后根据一种球的类型来贪心就行了。

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

#define ll long long

void exgcd(int a, int b, int& x, int& y) {
	if (!b) {
		x = 1, y = 0;
	} else {
		exgcd(b, a % b, y, x);
		y -= (a / b) * x;
	}
}

int inv(int a, int n) {
	int x, y;
	exgcd(a, n, x, y);
	return (x < 0) ? (x + n) : (x);
}

const int Mod = 998244353;

template <const int Mod = :: Mod>
class Z {
	public:
		int v;

		Z() : v(0) {	}
		Z(int x) : v(x){	}
		Z(ll x) : v(x % Mod) {	}

		friend Z operator + (const Z& a, const Z& b) {
			int x;
			return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x));
		}
		friend Z operator - (const Z& a, const Z& b) {
			int x;
			return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x));
		}
		friend Z operator * (const Z& a, const Z& b) {
			return Z(a.v * 1ll * b.v);
		}
		friend Z operator ~(const Z& a) {
			return inv(a.v, Mod);
		}
		friend Z operator - (const Z& a) {
			return Z(0) - a;
		}
		Z& operator += (Z b) {
			return *this = *this + b;
		}
		Z& operator -= (Z b) {
			return *this = *this - b;
		}
		Z& operator *= (Z b) {
			return *this = *this * b;
		}
		friend boolean operator == (const Z& a, const Z& b) {
			return a.v == b.v;
		} 
};

Z<> qpow(Z<> a, int p) {
	Z<> rt = Z<>(1), pa = a;
	for ( ; p; p >>= 1, pa = pa * pa) {
		if (p & 1) {
			rt = rt * pa;
		}
	}
	return rt;
}

typedef Z<> Zi;

const int N = 1e5 + 5;

int n;
char s[N * 3];

int main() {
	scanf("%d", &n);
	scanf("%s", s + 1);
	int r, g, b, rg, rb, gb;
	r = g = b = 0;
	rg = rb = gb = 0;
	Zi ans = 1;
	for (int i = 1; i <= n; i++)
		ans *= i;
	for (int i = 1; i <= 3 * n; i++) {
		if (s[i] == ‘R‘) {
			if (gb) {
				ans *= gb--;
			} else if (g) {
				ans *= g--;
				rg++;
			} else if (b) {
				ans *= b--;
				rb++;
			} else {
				r++;
			}
		} else if (s[i] == ‘G‘) {
			if (rb) {
				ans *= rb--;
			} else if (r) {
				ans *= r--;
				rg++;
			} else if (b) {
				ans *= b--;
				gb++;
			} else {
				g++;
			}
		} else if (s[i] == ‘B‘) {
			if (rg) {
				ans *= rg--;
			} else if (r) {
				ans *= r--;
				rb++;
			} else if (g) {
				ans *= g--;
				gb++;
			} else {
				b++;
			}
		}
	}
	printf("%d
", ans.v);
	return 0;
}

Problem C Numbers on a Circle

  考虑倒着做,操作变成一个数减去两边的和,如果一个数可以操作,那么它两边的数都不能操作。所以要么它达到它目标的值,要么它比两边的数小。

  不难发现操作1次,要么折半,要么达到目标的值,要么判出无解,所以总时间复杂度$O(nlog V)$。

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

const int N = 2e5 + 5;

int n;
int A[N];
int B[N];
boolean inq[N];

boolean check(int p) {
	return B[p] >= B[(p + n - 1) % n] + B[(p + 1) % n];
}

int main() {
	scanf("%d", &n);
	for (int i = 0; i < n; i++) {
		scanf("%d", A + i);
	}
	for (int i = 0; i < n; i++) {
		scanf("%d", B + i);
	}
	queue<int> Q;
	for (int i = 0; i < n; i++) {
		if (check(i)) {
			inq[i] = true;
			Q.push(i);
		}
	}
	long long ans = 0;
	while (!Q.empty()) {
		int p = Q.front();
		Q.pop();
		inq[p] = false;
		int pre = (p + n - 1) % n, suf = (p + 1) % n;
		int sum = B[pre] + B[suf];
		int t = max(0, (B[p] - A[p]) / sum);
		if (!t && B[p] != A[p]) {
			puts("-1");
			return 0;
		}
		ans += t;
		B[p] -= sum * t;
		if (!inq[pre] && check(pre)) {
			inq[pre] = true;
			Q.push(pre);
		}
		if (!inq[suf] && check(suf)) {
			inq[suf] = true;
			Q.push(suf);
		}
	}
	for (int i = 0; i < n; i++) {
		if (A[i] ^ B[i]) {
			puts("-1");
			return 0;
		}
	}
	printf("%lld
", ans);
	return 0;
}

Problem D Sorting a Grid

  考虑第一次移动需要达到的条件:属于目标同一行的数不在同一列。

  问题相当于给这样一个图染色:有$nm$个点,如果$(i,j)$和$(x, y)$有边相邻当且仅当它们属于同一行或者属于目标同一行。

  它所在的颜色标号等于它被换到的列号。

  考虑每次标出一种颜色。这个可以转成匹配问题,把原图的每个点看成边,两端点分别是它所在的两个团。

  这是一个正则二分图,所以必定有解。

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

const int inf = (signed) (~0u >> 1);

typedef class Edge {
	public:
		int ed, nx, r;

		Edge() {	}
		Edge(int ed, int nx, int r) : ed(ed), nx(nx), r(r) {	}
} Edge;

typedef class MapManager {
	public:
		int *h;
		vector<Edge> E;

		MapManager() {	}
		MapManager(int n) {
			h = new int[(n + 1)];
			memset(h, -1, sizeof(int) * (n + 1));
		}
		~MapManager() {
			delete[] h;
			E.clear();
		}

		void add_edge(int u, int v, int r) {
			E.emplace_back(v, h[u], r);
			h[u] = (signed) E.size() - 1;
		}
		void add_arc(int u, int v, int r) {
			add_edge(u, v, r);
			add_edge(v, u, 0);
		}
		Edge& operator [] (int p) {
			return E[p];
		}
} MapManager;

typedef class Network {
	public:
		int S, T;
		MapManager g;

		int *d, *h;

		Network(int S, int T) : S(S), T(T), g(T) {
			d = new int[(T + 1)];
			h = new int[(T + 1)];
		}
		~Network() {
			delete[] d;
			delete[] h;
		}

		boolean bfs() {
			queue<int> Q;
			memset(d, -1, sizeof(int) * (T + 1));
			d[S] = 0;
			Q.push(S);
			while (!Q.empty()) {
				int e = Q.front();
				Q.pop();
				for (int i = g.h[e], eu; ~i; i = g[i].nx) {
					eu = g[i].ed;
					if (!g[i].r || ~d[eu])
						continue;
					d[eu] = d[e] + 1;
					Q.push(eu);
				}
			}
			return d[T] != -1;
		}
		
		int dfs(int p, int mf) {
			if (p == T || !mf)
				return mf;
			int flow = 0, f;
			for (int& i = h[p], j, e; ~i; (i != -1) && (i = g[i].nx)) {
				e = g[i].ed, j = i;
				if (g[i].r && d[e] == d[p] + 1 && (f = dfs(e, min(mf, g[i].r))) > 0) {
					g[j].r -= f;
					g[j ^ 1].r += f;
					flow += f;
					mf -= f;
					if (!mf)
						break;
				}
			}
			return flow;
		}

		int dinic() {
			int rt = 0;
			while (bfs()) {
				for (int i = 0; i <= T; i++)
					h[i] = g.h[i];
				rt += dfs(S, inf);
			}
			return rt;
		}

		void add_edge(int u, int v, int r) {
			g.add_arc(u, v, r);
		}
} Network;

const int N = 105;

int n, m;
int a[N][N];
int b[N][N];
int c[N][N];
int id[N][N];
int col[N][N];

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			scanf("%d", a[i] + j);
		}
	}
	for (int c = 1, T; c <= m; c++) {
		Network	network (0, T = n + n + 1);
		for (int i = 1; i <= n; i++)
			network.add_edge(0, i, 1);
		for (int i = 1; i <= n; i++)
			network.add_edge(i + n, T, 1);
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= m; j++) {
				if (!col[i][j]) {
					network.add_edge(i, (a[i][j] + m - 1) / m + n, 1);
					id[i][j] = (signed) network.g.E.size() - 1;
				}
			}
		}
		network.dinic();
		MapManager& g = network.g;
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= m; j++) {
				if (!col[i][j] && g[id[i][j]].r) {
					col[i][j] = c;
				}
			}
		}
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			b[i][col[i][j]] = a[i][j];
		}
	}
	for (int i = 1; i <= n; i++, putchar(‘
‘)) {
		for (int j = 1; j <= m; j++) {
			printf("%d ", b[i][j]);	
			c[(b[i][j] + m - 1) / m][j] = b[i][j];
		}
	}
	for (int i = 1; i <= n; i++, putchar(‘
‘)) {
		for (int j = 1; j <= m; j++) {
			printf("%d ", c[i][j]);	
		}
	}
	return 0;
}

Problem E Reversing and Concatenating

  假设最小的字符为a,如果末尾有$a$,那么可以是$a$的数量变为之前的$2^{K}$倍,否则要先用一次操作使得它在末尾。

  你发现使得$a$的长度达到这个长,方案是唯一的。

  你枚举开始可能的串,这个至多有$O(n)$个。直接计算就行了。

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

const int N = 5005;

int n, K;
int mxR[N];
char s[N << 1], t[N], ans[N];

void check(char* s, int k, char min_c) {
	int L = 0, p = n;
	while (s[n - L] == min_c)
		L++, p--;
	L <<= k;
	if (L >= n) {
		for (int i = 1; i <= n; i++)
			putchar(min_c);
		putchar(‘
‘);
		exit(0);
	}
	for (int i = 1; i <= L; i++) {
		t[i] = min_c;
	}
	for (int i = L + 1; i <= n; i++)
		t[i] = s[p--];
	for (int i = 1; i <= n; i++)
		if (t[i] ^ ans[i]) {
			if (t[i] > ans[i]) {
				return;
			}
			break;
		}
	for (int i = 1; i <= n; i++)
		ans[i] = t[i];
}

int main() {
	scanf("%d%d", &n, &K);
	scanf("%s", s + 1);
	for (int i = 1; i <= n; i++)
		s[2 * n - i + 1] = s[i];
	char x = ‘z‘, y = ‘a‘;
	for (int i = 1; i <= n; i++) {
		x = min(x, s[i]);
		y = max(y, s[i]);
	}
	if (K >= 20 || x == y) {
		for (int i = 1; i <= n; i++)
			putchar(x);
		putchar(‘
‘);
		return 0;
	}
	ans[1] = ‘z‘ + 1;
	if (s[n] == x)
		check(s, K, x);
	int mxL = 0;
	for (int i = 1, j = 1; i <= n; i = j) {
		if (s[i] != x) {
			j++;
			continue;
		}
		while (s[j] == s[i])
			j++;
		mxR[i] = j - i;
		mxL = max(mxL, mxR[i]);
	}
	for (int i = 1; i <= n; i++) {
		if (mxR[i] == mxL) {
			check(s + (n - i + 1), K - 1, x);
		}
	}
	puts(ans + 1);
	return 0;
}

Problem F Counting of Subarrays

  可以先把问题转化成,你可以选择至少$l$个数,将它们合成一个数,问有多少个区间能合成一个数。

  考虑如何判断一个区间是否可行:

  • 取最小的元素$x$的连续段,假设长度为$L$,那么至多可以合成$lfloor L / l floor$个$x + 1$。
  • 递归执行。

  考虑怎么计算数量,考虑一个区间在它被合成一个可能的最小的数的时候计算。

  假设当前序列中最小的数位$x$,每次计算能合成的最小数为$x + 1$的区间个数,然后把$x$的连续段缩成若干个$x + 1$。

  要计算合成的数位$x + 1$的区间个数,只用求选择的连续至少$l$或者$1$个$x$的方案数。

  缩数后为了保证不算重,相当于要求每次选择的区间不能被这个连续段包含。把被包含的方案数减去。然后计算这个连续段内产生$k$个$x + 1$的可能的左端点数和右端点数。

  时间复杂度$O(nlog n)$

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

const int N = 2e5 + 5;

#define pii pair<int, int>
#define ll long long

typedef class Segment {
	public:
		int l, r, x, y;

		Segment() {	}
		Segment(int l, int r, int x, int y) : l(l), r(r), x(x), y(y) {	}

		boolean operator < (Segment b) const {
			return l < b.l;
		}
} Segment;

int n, L;
int a[N];
pii b[N];

ll calc(vector<pii>& a) {
	ll ret = 0, sum = 0;
	for (int l = 0, r = L - 1; r < (signed) a.size(); l++, r++) {
		ret += (sum += a[l].first) * a[r].second;
	}
	return ret;
}

ll ans = 0;
int main() {
	scanf("%d%d", &n, &L);
	for (int i = 1; i <= n; i++) {
		scanf("%d", a + i);
		b[i] = pii(a[i], i);
	}
	ans = n;
	sort(b + 1, b + n + 1);
	int pos = 1, val;
	vector<Segment> vcur, vnxt, vadd;
	while (true) {
		if (!vcur.size()) {
			if (pos > n) {
				break;
			} else {
				val = b[pos].first;
			}
		} else {
			val++;
		}
//		cerr << pos << " " << val << ‘
‘;
		vadd.clear();
		while (pos <= n && b[pos].first == val)
			vadd.emplace_back(b[pos].second, b[pos].second, 1, 1), pos++;
		vnxt.resize(vcur.size() + vadd.size());
		merge(vcur.begin(), vcur.end(), vadd.begin(), vadd.end(), vnxt.begin());
		swap(vcur, vnxt);
		vnxt.clear();
		int num = vcur.size();
		for (int i = 0, j = 0; i < num; i = ++j) {
			while (j < num - 1 && vcur[j].r + 1 == vcur[j + 1].l)
				j++;
			int len = j - i + 1, cnt = len / L;
			if (cnt) {
				vector<pii> tmp;
				for (int k = i; k <= j; k++)
					tmp.emplace_back(vcur[k].x, vcur[k].y);
				ans += calc(tmp);
				tmp.clear();
				tmp.resize(cnt, pii(0, 0));
				for (int k = L - 1; k < len; k++) {
					tmp[cnt - (k - L + 1) / L - 1].first += vcur[j - k].x;
				}
				for (int k = L - 1; k < len; k++) {
					tmp[(k - L + 1) / L].second += vcur[i + k].y;
				}
				ans -= calc(tmp);
				for (int k = 0; k < cnt; k++)
					vnxt.emplace_back(vcur[i].l + k, vcur[i].l + k, tmp[k].first, tmp[k].second);
				vnxt.back().r = vcur[j].r;
			}
		}
		swap(vcur, vnxt);
		vnxt.clear();
	}
	printf("%lld
", ans);
	return 0;
}

以上是关于AtCoder Grand Contest 037 简要题解的主要内容,如果未能解决你的问题,请参考以下文章

Atcoder Grand Contest 037B(DP,组合数学,思维)

markdown AtCoder Grand Contest 016

AtCoder Grand Contest 005

AtCoder Grand Contest 006

AtCoder Grand Contest 008 题解

AtCoder Grand Contest 025 Problem D