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\n", 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\n", ans.v);
	return 0;
}

Problem C Numbers on a Circle

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

  不难发现操作1次,要么折半,要么达到目标的值,要么判出无解,所以总时间复杂度$O(n\log 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\n", 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(‘\n‘)) {
		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(‘\n‘)) {
		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(‘\n‘);
		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(‘\n‘);
		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 \rfloor$个$x + 1$。
  • 递归执行。

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

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

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

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

  时间复杂度$O(n\log 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 << ‘\n‘;
		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\n", ans);
	return 0;
}

原文地址:https://www.cnblogs.com/yyf0309/p/11619998.html

时间: 2024-10-08 16:57:40

AtCoder Grand Contest 037 简要题解的相关文章

AtCoder Grand Contest 038 简要题解

从这里开始 比赛目录 Problem A 01 Matrix Code #include <bits/stdc++.h> using namespace std; typedef bool boolean; const int N = 1e3 + 5; int W, H, A, B; int main() { scanf("%d%d%d%d", &W, &H, &A, &B); for (int i = 0; i < W; i++) {

AtCoder Beginner Contest 155 简要题解

AtCoder Beginner Contest 155 A:签到失败,WA一次. int main() { int a, b, c; cin >> a >> b >> c; if(a == b && b == c) cout << "No"; else if(a == b || a == c || b == c) cout << "Yes"; else cout << &quo

AtCoder Grand Contest 043 部分题解

这场打的好爽,rank \(299\),涨了 \(141\) AGC043A 乍一看有点不知所措.BFS?暴力? 让我们冷静分析一下.要达成目标,必须有至少一条从左上到右下的路径. 感受一下: xxx.. ..x.. ..xx. ...x. ...xx 注意到操作是同时对一个矩形区域操作.不难发现:这样可以对路径上任意一段连续序列取反. 怎样操作最优呢? 根据首尾,可以分为四种情况: #.#.# (答案:3) .#.#. (答案:2) #.#. (答案:2) .#.# (答案:2) 综上,答案就

AtCoder Grand Contest 025 Problem D

www.cnblogs.com/shaokele/ AtCoder Grand Contest 025 Problem D Time Limit: 2 Sec Memory Limit: 1024 MB Description Takahashi is doing a research on sets of points in a plane. Takahashi thinks a set \(S\) of points in a coordinate plane is a good set w

AtCoder Grand Contest 024 Problem E(动态规划)

www.cnblogs.com/shaokele/ AtCoder Grand Contest 024 Problem E Time Limit: 2 Sec Memory Limit: 1024 MB Description Find the number of the possible tuples of sequences (\(A_0,A_1,-,A_N\)) that satisfy all of the following conditions, modulo \(M\): ? Fo

AtCoder Grand Contest 011

AtCoder Grand Contest 011 upd:这篇咕了好久,前面几题是三周以前写的... AtCoder Grand Contest 011 A - Airport Bus 翻译 有\(n\)个乘客到达了飞机场,现在他们都要坐车离开机场.第\(i\)个乘客到达的时间是\(T_i\),一个乘客必须在\([T_i,T_i+k]\)时刻做到车,否则他会生气.一辆车最多可以坐\(C\)个人.问最少安排几辆车可以让所有人都不生气. 题解 从前往后贪心即可. #include<iostream

AtCoder Grand Contest 014

AtCoder Grand Contest 014 A - Cookie Exchanges 有三个人,分别有\(A,B,C\)块饼干,每次每个人都会把自己的饼干分成相等的两份然后给其他两个人.当其中有一个人的饼干数量是奇数的时候停止,求会进行几次这样子的操作,或者会永远进行下去. 首先无解的情况一定是三个数都是相等的偶数. 否则直接暴力模拟就行了.(盲猜答案不会很大) 证明一下答案的范围:不妨令\(A\le B\le C\),那么最大值和最小值之间的差就是\(C-A\),那么执行完一次操作之后

【Atcoder Grand Contest 020 E】 Encoding Subsets

Atcoder Grand Contest 020 E 题意:给一个\(0-1\)字符串,如果其中有一段重复,就可以表示成\((\)这一块的表示\(\times\)出现次数\()\). 问这个字符串的所有子集中有多少种表示方法. 思路:考虑\(dp(s)\)表示字符串\(s\)的答案. 那么我们得考虑第一个表示成的位置是什么. ①第一位就是表示的第一位,不参与循环.那么转移到\(dp(s.substr(1))\),并且如果这位是\(1\),那么乘上\(2\),因为这位可能是\(0\). ②一个前

AtCoder Grand Contest 016

AtCoder Grand Contest 016 A - Shrinking 你可以进行一个串的变换,把一个长度为\(n\)的串\(S\)可以变成长度为\(n-1\)的串\(T\),其中\(T_i\)要么是\(S_i\)要么是\(S_{i+1}\). 现在问你最少进行多少次这个操作,能够使最终得到的\(T\)只由一个字符构成. \(|S|\le 100\) 首先枚举最终字符是哪一个.那么首先在\(S\)末尾加上一个这个字符,那么这个最小步数等于对于所有位置而言,离它最近的枚举的字符到这个位置的