【AtCoder】AtCoder Grand Contest 036 题解

【比赛链接】

【题解链接】

【A】 Triangle

【思路要点】

  • 注意到对于任意一个格点三角形,我们都可以找到一个点,将其平移至原点处后,另外两个点在同一象限内,或在该象限相邻的坐标轴上。
  • 因此,可以不失一般性地假定一个点在 ( 0 , 0 ) (0,0) (0,0) 处。
  • 令另外两点的坐标为 ( x , y ) , ( a , b ) (x,y),(a,b) (x,y),(a,b) ,则要求 x b − y a = S xb-ya=S xbya=S
  • 简单构造一组解即可。
  • 时间复杂度 O ( 1 ) O(1) O(1)

【代码】

#include<bits/stdc++.h>
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 main() {
	ll s; read(s); int lim = 1e9;
	int x = s / lim; if (s % lim) x++;
	int b = s / x; if (s % x) b++;
	int y = 1ll * x * b - s;
	printf("%d %d %d %d %d %d\n", 0, 0, x, y, 1, b);
	return 0;
}

【B】 Do Not Duplicate

【思路要点】

  • A n s i Ans_i Ansi 表示对空串加入 a i , a i + 1 , … , a N a_i,a_{i+1},\dots,a_N ai,ai+1,,aN 后得到的数组, A n s 0 Ans_0 Ans0 表示空串。
  • 可以发现, K K K 取任意值时,得到的结果总是 A n s i Ans_i Ansi 中的一个。
  • 这是因为对于 A n s 0 Ans_0 Ans0 加入 a 1 , a 2 , … , a N a_1,a_2,\dots,a_N a1,a2,,aN 后将得到 A n s 1 Ans_1 Ans1 ;对于 A n s i Ans_{i} Ansi ,在加入 A n s i , 1 Ans_{i,1} Ansi,1 第一次出现的位置 j j j 时,串将变为空串,于是会得到 A n s j + 1 Ans_{j+1} Ansj+1
  • 在这样的关系上倍增或是找周期即可。
  • 时间复杂度 O ( N L o g K ) O(NLogK) O(NLogK) O ( N ) O(N) O(N)

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const int MAXLOG = 60;
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, a[MAXN], f[MAXN], nxt[MAXN];
int dest[MAXN][MAXLOG]; ll k;
int main() {
	read(n), read(k);
	for (int i = 1; i <= n; i++)
		read(a[i]);
	for (int i = n; i >= 1; i--)
		f[a[i]] = i;
	for (int i = n; i >= 1; i--) {
		nxt[i] = f[a[i]];
		f[a[i]] = i;
	}
	dest[0][0] = 1;
	for (int i = n; i >= 1; i--) {
		if (nxt[i] <= i) {
			if (nxt[i] == n) dest[i][0] = 0;
			else dest[i][0] = nxt[i] + 1;
		} else {
			if (nxt[i] == n) dest[i][0] = 1;
			else dest[i][0] = dest[nxt[i] + 1][0];
		}
	}
	for (int p = 1; p < MAXLOG; p++)
	for (int i = 0; i <= n; i++)
		dest[i][p] = dest[dest[i][p - 1]][p - 1];
	int ans = 0;
	for (int i = 0; i < MAXLOG; i++)
		if (k & (1ll << i)) ans = dest[ans][i];
	memset(f, 0, sizeof(f));
	static int b[MAXN]; int tot = 0;
	if (ans == 0) return 0;
	for (int i = ans; i <= n; i++)
		if (f[a[i]] == 0) {
			b[++tot] = a[i];
			f[a[i]] = tot;
		} else {
			int pos = f[a[i]];
			while (tot >= pos) f[b[tot--]] = 0;
		}
	for (int i = 1; i <= tot; i++)
		printf("%d ", b[i]);
	return 0;
}

【C】 GP 2

【思路要点】

  • 一个局面合法有一些显然的必要条件:
    ( 1 ) (1) (1) 、所有数的和为 3 M 3M 3M
    ( 2 ) (2) (2) 、所有数的最大值不超过 2 M 2M 2M
    ( 3 ) (3) (3) 、所有数中奇数的个数不超过 M M M
  • 考虑对最大值和除去该数的剩余奇数中的最大值进行分类讨论,可以用归纳法证明,这些条件也是充分的。
  • 考虑如何计数,如果没有限制 ( 2 ) (2) (2) ,则可以枚举奇数的个数 i i i ,贡献即为 ( N i ) × ( 3 M − i 2 + N − 1 N − 1 ) \binom{N}{i}\times\binom{\frac{3M-i}{2}+N-1}{N-1} (iN)×(N123Mi+N1)
  • 那么对限制 ( 2 ) (2) (2) 进行容斥即可,即计算满足限制 ( 1 ) , ( 3 ) (1),(3) (1),(3) ,且最大值超过 2 M 2M 2M 的方案数,同样可以枚举奇数的个数 i i i 进行计算。
  • 时间复杂度 O ( N + M ) O(N+M) O(N+M)

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e6 + 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("");
}
int fac[MAXN], inv[MAXN];
int power(int x, int y) {
	if (y == 0) return 1;
	int tmp = power(x, y / 2);
	if (y % 2 == 0) return 1ll * tmp * tmp % P;
	else return 1ll * tmp * tmp % P * x % P;
}
int binom(int x, int y) {
	if (y > x) return 0;
	else return 1ll * fac[x] * inv[y] % P * inv[x - y] % P;
}
void init(int n) {
	fac[0] = 1;
	for (int i = 1; i <= n; i++)
		fac[i] = 1ll * fac[i - 1] * i % P;
	inv[n] = power(fac[n], P - 2);
	for (int i = n - 1; i >= 0; i--)
		inv[i] = inv[i + 1] * (i + 1ll) % P;
}
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
int main() {
	int n, m, ans = 0;
	read(n), read(m);
	init(n + 3 * m);
	int s = 3 * m;
	for (int i = 0; i <= m && i <= n; i++) {
		if ((s - i) & 1) continue;
		int tmp = (s - i) / 2;
		update(ans, 1ll * binom(n, i) * binom(tmp + n - 1, n - 1) % P);
	}
	s -= 2 * m;
	for (int i = 0; i <= m && i <= n; i++) {
		if ((s - i) & 1) continue;
		int tmp = (s - i) / 2;
		if (i != 0) update(ans, P - 1ll * n * binom(n - 1, i - 1) % P * binom(tmp + n - 1, n - 1) % P);
		if (tmp != 0) update(ans, P - 1ll * n * binom(n - 1, i) % P * binom(tmp + n - 2, n - 1) % P);
	}
	writeln(ans);
	return 0;
}

【D】 Negative Cycle

【思路要点】

  • 考虑 0 0 0 号节点到每个节点 i i i 的最短路 d i s i dis_i disi ,由于本来存在的边, d i s i dis_i disi 是递减的,且相邻的位置相差至多为 1 1 1
  • 考虑一个确定的最短路序列 d i s i dis_i disi ,我们需要删掉一些边以确保其不能被更新,所花费的代价即为 d i s i dis_i disi 对应的代价。
  • 计算代价的过程是直观的,考虑相邻的两段 d i s i dis_i disi 相同的区间 [ a , b ] , [ b + 1 , c ] [a,b],[b+1,c] [a,b],[b+1,c] ,其中 [ b + 1 , c ] [b+1,c] [b+1,c] 中连出的边需要删掉连向 [ b + 2 , c ] [b+2,c] [b+2,c] 的向前的边,以及连向 [ 0 , a − 1 ] [0,a-1] [0,a1] 的向后的边。
  • 不难发现在状态中计入 a , b a,b a,b ,转移时枚举 c c c ,利用部分和优化即可得到一个 O ( N 3 ) O(N^3) O(N3) 的动态规划解法。
  • 时间复杂度 O ( N 3 ) O(N^3) O(N3)

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 505;
const long long INF = 1e18;
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, a[MAXN][MAXN];
ll s[MAXN][MAXN], cost[MAXN][MAXN], dp[MAXN][MAXN];
int main() {
	read(n);
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j < i; j++)
			read(a[i][j]);
		for (int j = i + 1; j <= n; j++)
			read(a[i][j]);
	}
	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= n; j++)
		s[i][j] = s[i][j - 1] + a[i][j];
	for (int i = 0; i <= n; i++)
	for (int j = i + 1; j <= n; j++)
		dp[i][j] = INF;
	for (int i = 1; i <= n; i++)
	for (int j = i; j <= n; j++) {
		ll res = 0;
		for (int k = i; k <= j; k++)
			res += s[k][j] - s[k][k];
		cost[i][j] = res;
	}
	for (int i = 1; i <= n; i++)
		dp[0][i] = cost[1][i];
	for (int i = 0; i <= n - 1; i++)
	for (int j = i + 1; j <= n; j++) {
		ll tmp = dp[i][j];
		for (int k = j + 1; k <= n; k++) {
			tmp += s[k][i];
			chkmin(dp[j][k], tmp + cost[j + 1][k]);
		}
	}
	ll ans = INF;
	for (int i = 0; i <= n - 1; i++)
		chkmin(ans, dp[i][n]);
	writeln(ans);
	return 0;
}

【E】 ABC String

【思路要点】

  • 不难发现应该将连续的字符当成一个看待,记此时 A A A 的个数为 a a a B B B 的个数为 b b b C C C 的个数为 c c c ,不失一般性地设 a ≤ b ≤ c a\leq b\leq c abc
  • 考虑选定一个 A A A 的集合,删除其余的 A A A ,并将相同的字符重新并起来,由于删除一个 A A A 至多使得 b , c b,c b,c 减少 1 1 1 ,因此依然有 a ≤ b , a ≤ c a\leq b,a\leq c ab,ac
  • 仍然设 a ≤ b ≤ c a\leq b\leq c abc ,考虑删除一些 B , C B,C B,C ,使得字符串满足条件。
  • b = c b=c b=c ,我们可以不断地删除不会使得字符串中出现 A A AA AA B C BC BC C B CB CB
  • 否则,即 b &lt; c b&lt;c b<c ,记总共 a + 1 a+1 a+1 段字符中包含 B B B 的段数为 x x x ,中间的 a − 1 a-1 a1 段字符中仅由一个 C C C 组成的段数为 y y y
  • x &lt; y x&lt;y x<y ,显然是无法通过删除一些 B , C B,C B,C 使得 b = c b=c b=c 的,否则,就说明包含 B B B 的段数不少于仅由一个 C C C 组成的段数,可以不断地删除不会使得字符串中出现连续相同字符的 C C C ,使得 b = c b=c b=c
  • 因此,我们需要在原串中选出尽量多的 A A A ,满足得到的字符串中 x ≥ y x\geq y xy
  • 考虑先选取所有的 A A A ,若 x ≥ y x\geq y xy ,则找到的一定是最优解,否则记 d = y − x d=y-x d=yx ,删去 d d d 个与单独的 C C C 相邻的 A A A 即可。
  • 时间复杂度 O ( ∣ S ∣ ) O(|S|) O(S)

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 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("");
}
char s[MAXN]; int n;
vector <pair <int, int>> mod;
void modify(char *s, int n, int x, int y) {
	for (int i = 1; i <= n; i++)
		if (s[i] == x + 'A') s[i] = y + 'A';
		else if (s[i] == y + 'A') s[i] = x + 'A';
}
void init() {
	scanf("%s", s + 1);
	int m = strlen(s + 1);
	int cnt[3] = {0, 0, 0};
	for (int i = 1; i <= m; i++)
		if (s[n] != s[i]) {
			s[++n] = s[i];
			cnt[s[i] - 'A']++;
		}
	s[n + 1] = 0;
	if (cnt[0] > cnt[1]) swap(cnt[0], cnt[1]), modify(s, n, 0, 1), mod.emplace_back(0, 1);
	if (cnt[1] > cnt[2]) swap(cnt[1], cnt[2]), modify(s, n, 1, 2), mod.emplace_back(1, 2);
	if (cnt[0] > cnt[1]) swap(cnt[0], cnt[1]), modify(s, n, 0, 1), mod.emplace_back(0, 1);
	if (cnt[0] == 0) puts(""), exit(0);
}
void sela() {
	int b = 0, c = 0;
	static int pos[MAXN]; int cnt = 0;
	for (int i = 1; i <= n; i++)
		if (s[i] == 'A') pos[++cnt] = i;
	for (int i = 1; i <= cnt; i++) {
		bool occur = false;
		for (int j = pos[i - 1] + 1; j <= pos[i] - 1; j++)
			occur |= s[j] == 'B';
		b += occur;
	}
		bool occur = false;
		for (int j = pos[cnt] + 1; j <= n; j++)
			occur |= s[j] == 'B';
		b += occur;
	for (int i = 1; i <= cnt - 1; i++)
		c += pos[i] == pos[i + 1] - 2 && s[pos[i] + 1] == 'C';
	if (b >= c) return;
	int lft = c - b, tmp = n; n = 0;
	for (int i = 1; i <= tmp; i++)
		if (s[i] == 'A') {
			if (lft != 0 && n >= 2 && s[n] == 'C' && s[n - 1] == 'A') lft--;
			else s[++n] = s[i];
		} else if (s[n] != s[i]) s[++n] = s[i];
	assert(lft == 0);
}
void delc() {
	int delta = 0;
	for (int i = 1; i <= n; i++)
		if (s[i] == 'C') delta++;
		else if (s[i] == 'B') delta--;
	assert(delta >= 0);
	int tmp = n; n = 0; s[tmp + 1] = 0;
	for (int i = 1; i <= tmp; i++)
		if (s[i] == 'C') {
			if (delta != 0 && ((n >= 1 && s[n] != s[i + 1]) || n == 0)) delta--;
			else s[++n] = s[i];
		} else s[++n] = s[i];
	assert(delta == 0);
}
void delb() {
	int a = 0, b = 0, c = 0;
	for (int i = 1; i <= n; i++)
		if (s[i] == 'C') c++;
		else if (s[i] == 'B') b++;
		else a++;
	assert(b == c && b >= a);
	int tmp = n; n = 0; s[tmp + 1] = -1;
	for (int i = 1; i <= tmp; i++)
		if (s[i] == 'A') s[++n] = s[i];
		else if (b > a && n >= 1 && s[n] != 'A' && s[i] != s[n] && s[i + 1] != s[n - 1]) n--, b--, c--;
		else s[++n] = s[i];
	s[n + 1] = 0;
}
int main() {
	init();
	sela();
	delc();
	delb();
	while (!mod.empty()) {
		modify(s, n, mod.back().first, mod.back().second);
		mod.pop_back();
	}
	printf("%s\n", s + 1);
	return 0;
}

【F】 Square Constraints

【思路要点】

  • 考虑没有下界的情况,显然每一个点可以匹配的位置是一个前缀,记这些前缀的长度分别为 a 0 , a 1 , a 2 , …   ( a 0 ≤ a 1 ≤ a 2 ≤ … &ThinSpace; ) a_0,a_1,a_2,\dots\ (a_0\leq a_1\leq a_2\leq\dots) a0,a1,a2, (a0a1a2) ,那么方案数即为 ∏ i ( a i − i ) \prod_{i}(a_i-i) i(aii)
  • f ( i ) f(i) f(i) 表示平方和 ≤ ( 2 N ) 2 \leq (2N)^2 (2N)2 可以匹配的前缀长度, g ( i ) g(i) g(i) 表示平方和 &lt; N 2 &lt;N^2 <N2 可以匹配的前缀长度,若 i ≥ N i\geq N iN 则为 0 0 0
  • 考虑对下界进行容斥,一个直观地想法是选择 i i i 个位置取 g ( i ) g(i) g(i) ,其余位置取 f ( i ) f(i) f(i) ,排序后计算匹配数,乘上 ( − 1 ) i (-1)^i (1)i 计入答案。
  • 一点关键的观察是如果将不是 0 0 0 f ( i ) , g ( i ) f(i),g(i) f(i),g(i) ,共 3 N 3N 3N 个数排序,所有 ≤ N − 1 \leq N-1 N1 i i i 对应的 f ( i ) f(i) f(i) 会排在最后 N N N 个位置,这是因为这些数 ≥ N \geq N N ,而其余数 ≤ N \leq N N
  • 据此,枚举上面选取的位置数 i i i ,我们可以直观地用一个 O ( N 2 ) O(N^2) O(N2) 的动态规划加速上面的枚举,即记 d p j , k dp_{j,k} dpj,k 表示考虑了排在前面的 j j j 个位置,选择了 k k k g ( i ) g(i) g(i) 的方案数,转移时将对应的 f ( i ) , g ( i ) f(i),g(i) f(i),g(i) 一同考虑即可。
  • 时间复杂度 O ( N 3 ) O(N^3) O(N3)

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 255;
const int MAXM = 505;
const int INF  = 1e9;
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, Q, P;
int dp[MAXM][MAXN];
pair <int, int> a[MAXM];
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
int main() {
	read(n), read(Q), P = INF / Q * Q;
	int p = 2 * n - 1, q = 2 * n - 1;
	for (int i = 0; i <= 2 * n - 1; i++) {
		while (i * i + p * p > (2 * n) * (2 * n) && p >= 0) p--;
		while (i * i + q * q >= n * n && q >= 0) q--;
		if (q == -1) a[++m] = make_pair(p + 1, 0);
		else a[++m] = make_pair(q + 1, p + 1);
	}
	sort(a + 1, a + m + 1);
	int ans = 0;
	for (int k = 0; k <= n; k++) {
		memset(dp, 0, sizeof(dp)), dp[0][0] = 1;
		for (int i = 1, f = 0, g = 0; i <= m; f += a[i].second != 0, g += a[i].second == 0, i++)
		for (int j = 0; j <= k; j++) {
			int tmp = dp[i - 1][j];
			if (a[i].second == 0) update(dp[i][j], 1ll * tmp * (a[i].first - j - g) % P);
			else {
				if (j != k) update(dp[i][j + 1], 1ll * tmp * (a[i].first - j - g) % P);
				update(dp[i][j], 1ll * tmp * (a[i].second - k - n - (f - j)) % P);
			}
		}
		int now = dp[m][k];
		if (k & 1) update(ans, P - now);
		else update(ans, now);
	}
	writeln(ans % Q);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值