AtCoder Regular Contest 104 题解

A

题意简述

给定 \(A\)\(B\),求 \(X\)\(Y\) 使得 \(X+Y=A,X-Y=B\)

Solution

答案为 \(\frac{A+B}2\)\(\frac{A-B}2\)

Code

#include <bits/stdc++.h>

int a, b;

int main()
{
	std::cin >> a >> b;
	std::cout << (a + b) / 2 << " " << (a - b) / 2 << std::endl;
	return 0;
}

B

题意简述

给定一个 DNA 序列,串长不超过 \(5000\),求有多少个子区间,满足其存在一个排列与原子区间互补配对(A-TC-G)。

Solution

题目条件即为子区间内 AT 个数相等,CG 相等,直接枚举子区间,前缀和判断即可,\(O(n^2)\)

Code

#include <bits/stdc++.h>

const int N = 5005;

int n, sA[N], sC[N], sG[N], sT[N], ans;
char s[N];

int main()
{
	scanf("%d%s", &n, s + 1);
	for (int i = 1; i <= n; i++)
	{
		sA[i] = sA[i - 1] + (s[i] == 'A');
		sC[i] = sC[i - 1] + (s[i] == 'C');
		sG[i] = sG[i - 1] + (s[i] == 'G');
		sT[i] = sT[i - 1] + (s[i] == 'T');
	}
	for (int l = 1; l <= n; l++)
		for (int r = l; r <= n; r++)
			if (sA[r] - sA[l - 1] == sT[r] - sT[l - 1]
				&& sC[r] - sC[l - 1] == sG[r] - sG[l - 1])
					ans++;
	return std::cout << ans << std::endl, 0;
}

C

题意简述

\(N\) 个区间,端点为 \(1\sim2N\) 中的数值,每个端点被使用恰好一次。

额外条件:如果两个区间相交,则要求这两个区间等长。

给出这些区间的部分端点,求是否存在一种方案还原剩下的所有端点位置,使得上面的条件被满足。

\(N\le 100\)

Solution

结论:满足条件时,\(1\sim2N\)\(2N\) 个数可以被划分成若干个长度为偶数的段,使得没有任何区间的两端点分别在两个不同的段内,并且对于一个长度为 \(2x\) 的段 \([L,R]\),这个段内的区间符合 \([L,L+x]\)\([L+1,L+x+1]\)\([L+2,L+x+2]\)\(\dots\)\([R-x,R]\) 的形式。

直接分段 DP 即可,\(O(N^3)\)

不过这题有一个很大的坑点(一遍 AC 这题的人是变态):对于两个区间 \([L,*]\)\([*,R]\),我们不能强行让 \(L\)\(R\) 成为一个区间的两端点。

这个细节坑了不少人(也包括我)几十分钟。

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

const int N = 105, M = 205;

int n, a[N], b[N], c[M], o[M];
bool ava[M][M], f[M];

bool in(int l, int r, int x) {return l <= x && x <= r;}

int main()
{
	read(n);
	for (int i = 1; i <= n; i++) read(a[i]), read(b[i]);
	memset(c, -1, sizeof(c));
	for (int i = 1; i <= n; i++)
	{
		if (a[i] != -1)
		{
			if (c[a[i]] != -1) return puts("No"), 0;
			c[a[i]] = 0; o[a[i]] = i;
		}
		if (b[i] != -1)
		{
			if (c[b[i]] != -1) return puts("No"), 0;
			c[b[i]] = 1; o[b[i]] = i;
		}
		if (a[i] != -1 && b[i] != -1 && a[i] >= b[i])
			return puts("No"), 0;
	}
	for (int l = 1; l <= (n << 1); l += 2)
		for (int r = l + 1; r <= (n << 1); r += 2)
		{
			int d = (r - l + 1) >> 1; ava[l][r] = 1;
			for (int i = 1; i <= n; i++)
				if (a[i] != -1 && b[i] != -1)
				{
					if (in(l, r, a[i]) ^ in(l, r, b[i])) ava[l][r] = 0;
					if (in(l, r, a[i]) && in(l, r, b[i])
						&& b[i] - a[i] != d) ava[l][r] = 0;
				}
			for (int i = 1; i <= d; i++)
			{
				if (c[l + i - 1] == 1 || c[l + d + i - 1] == 0)
					ava[l][r] = 0;
				if (c[l + i - 1] == 0 && c[l + d + i - 1] == 1
					&& o[l + i - 1] != o[l + d + i - 1])
						ava[l][r] = 0;
			}
		}
	f[0] = 1;
	for (int i = 2; i <= (n << 1); i += 2)
		for (int j = 0; j < i; j += 2)
			f[i] |= f[j] && ava[j + 1][i];
	return puts(f[n << 1] ? "Yes" : "No"), 0;
}

D

题意简述

对于所有的 \(1\le x\le N\),求 \(1\sim N\) 的所有整数各选 \(0\sim K\) 个(不能一个都不选),选出的数平均数为 \(x\) 的方案数。

\(N,K\le 100\)

Solution

把所有数都减去 \(x\),转化成和为 \(0\),不难发现这么处理之后可以分为三批:

(1)\([1,N-x]\) 各选 \(0\sim K\) 个,贡献为正;

(2)\([1,x-1]\) 各选 \(0\sim K\) 个,贡献为负;

(3)选 \(0\sim K\)\(0\)

考虑处理前两部分,我们只需在前面先来一个 DP \(f_{i,j}\) 表示 \(1\sim i\) 的数和为 \(j\) 的方案数,前两部分即为 \(\sum_if_{N-x,i}f_{x-1,i}\)

设上式结果为 \(s\),则答案为 \((K+1)s-1\)

总时间复杂度 \(O(N^3K)\)

Code

#include <bits/stdc++.h>

const int N = 105, M = 6e5 + 5;

int n, k, EI, f[N][M], MX;

inline void add(int &a, const int &b) {if ((a += b) >= EI) a -= EI;}

inline void sub(int &a, const int &b) {if ((a -= b) < 0) a += EI;}

int main()
{
	std::cin >> n >> k >> EI;
	f[0][0] = 1; MX = k * n * (n + 1) / 2;
	for (int i = 1; i <= n; i++)
	{
		int mx = k * i * (i + 1) / 2, nx = (k + 1) * i;
		for (int j = 0; j <= mx; j++)
		{
			f[i][j] = f[i - 1][j]; if (j >= i) add(f[i][j], f[i][j - i]);
			if (j >= nx) sub(f[i][j], f[i - 1][j - nx]);
		}
	}
	for (int i = 1; i <= n; i++)
	{
		int ans = 0;
		for (int j = 0; j <= MX; j++)
			ans = (1ll * f[n - i][j] * f[i - 1][j] + ans) % EI;
		printf("%d\n", (int) ((1ll * ans * (k + 1) - 1 + EI) % EI));
	}
	return 0;
}

E

题意简述

\(N\) 个数的序列,第 \(i\) 个整数在 \([1,A_i]\) 间等概率随机,求该序列最长上升子序列长度的期望。

\(N\le 6\)\(A_i\le 10^9\)

Solution

考虑枚举这些数两两间的大小关系,具体枚举方法为先做一个贝尔数的搜索以确定相等关系,再全排列枚举大小顺序。

确定了这个之后显然已经确定了 LIS 的长度,现在要求的就是满足这组大小关系的方案数。

问题转化成给定 \(m\),求满足 \(x_i\le a_i\) 且严格递增的序列 \(x\) 个数。

这是一个经典问题,可以离散化值域分段之后组合数 DP。

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}

const int N = 8, EI = 1000000007;

int n, a[N], ans, bel[N], tot, p[N], r[N], mn[N], f[N], inv[N], cnt,
sze[N], mx[N], C[N][N], g[N][N];

void solve()
{
	for (int i = 1; i <= tot; i++) p[i] = i;
	do
	{
		for (int i = 1; i <= tot; i++) mn[i] = EI;
		for (int i = 1; i <= n; i++) r[i] = p[bel[i]],
			mn[p[bel[i]]] = Min(mn[p[bel[i]]], a[i]);
		int lis = 0;
		for (int i = 1; i <= n; i++)
		{
			f[i] = 0;
			for (int j = 1; j < i; j++)
				if (r[j] < r[i] && f[j] > f[i]) f[i] = f[j];
			f[i]++; if (f[i] > lis) lis = f[i];
		}
		for (int i = tot - 1; i >= 1; i--) mn[i] = Min(mn[i], mn[i + 1]);
		cnt = 0;
		for (int i = 1; i <= tot; i++)
		{
			if (mn[i] > mn[i - 1]) sze[++cnt] = mn[i] - mn[i - 1];
			mx[i] = cnt;
		}
		for (int i = 1; i <= cnt; i++)
		{
			C[i][0] = 1;
			for (int j = 1; j <= tot; j++)
				C[i][j] = 1ll * C[i][j - 1] * (sze[i] - j + 1)
					% EI * inv[j] % EI;
		}
		memset(g, 0, sizeof(g));
		for (int i = 0; i <= cnt; i++) g[0][i] = 1;
		for (int i = 1; i <= tot; i++)
			for (int j = 1; j <= cnt; j++)
			{
				for (int k = i; k >= 1; k--) if (j <= mx[k])
					g[i][j] = (1ll * g[k - 1][j - 1] * C[j][i - k + 1]
						+ g[i][j]) % EI;
				g[i][j] = (g[i][j] + g[i][j - 1]) % EI;
			}
		ans = (1ll * g[tot][cnt] * lis + ans) % EI;
	} while (std::next_permutation(p + 1, p + tot + 1));
}

void dfs_min(int dep)
{
	if (dep == n + 1) return solve();
	for (int i = 1; i <= tot; i++) bel[dep] = i, dfs_min(dep + 1);
	bel[dep] = ++tot; dfs_min(dep + 1); tot--;
}

int qpow(int a, int b)
{
	int res = 1;
	while (b)
	{
		if (b & 1) res = 1ll * res * a % EI;
		a = 1ll * a * a % EI;
		b >>= 1;
	}
	return res;
}

int main()
{
	read(n); inv[1] = 1;
	for (int i = 1; i <= n; i++) read(a[i]);
	for (int i = 2; i <= n; i++)
		inv[i] = 1ll * (EI - EI / i) * inv[EI % i] % EI;
	dfs_min(1);
	for (int i = 1; i <= n; i++) ans = 1ll * ans * qpow(a[i], EI - 2) % EI;
	return std::cout << ans << std::endl, 0;
}

F

简要题意

给定一个 \(N\) 个数的序列 \(H\)\(H_i\) 可以为 \([1,A_i]\) 的任何整数。

定义 \(P_i\) 表示满足 \(j<i\)\(H_j>H_i\) 的最大 \(j\),如果不存在则 \(P_i=-1\)

求有多少种不同的 \(P\) 数组。\(N\le 100\)\(A_i\le10^5\)

Solution

不难想到笛卡尔树,根为最大数的位置(相同情况下取最右位置)。可以注意到根即为最后一个 \(-1\) 的位置,这个位置上的数要顶到最大值。

于是定义 DP:\(f_{l,r,i}\) 表示区间 \([l,r]\) 内,强制所有的数不超过 \(i\)\(P\) 数组的取值方案数。

转移:\(f_{l,r,i}\leftarrow f_{l,mid-1,\min(i,A_{mid})}+f_{mid+1,r,\min(i,A_{mid})-1}\)

可以发现为了维持 \(H\) 数组一种特定的相互大小关系,在最坏情况下 \(H\) 数组的最大值不超过 \(N\),所以第三维可以只记录前 \(O(N)\) 个。\(O(N^4)\)

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

const int N = 110, M = 210, EI = 1e9 + 7;

int n, a[N], f[N][N][M];

int main()
{
	read(n);
	for (int i = 1; i <= n; i++)
	{
		read(a[i]);
		for (int j = 1; j <= 201; j++) f[i][i][j] = 1;
	}
	for (int l = n - 1; l >= 1; l--)
		for (int r = l + 1; r <= n; r++)
		{
			for (int j = 1; j <= 201; j++)
				for (int mid = l; mid <= r; mid++)
				{
					int x = j, delta = 1; if (a[mid] < x) x = a[mid];
					if (l < mid) delta = 1ll * delta * f[l][mid - 1][x] % EI;
					if (mid < r) delta = 1ll * delta *
						f[mid + 1][r][x <= 200 ? x - 1 : x] % EI;
					if ((f[l][r][j] += delta) >= EI)
						f[l][r][j] -= EI;
				}
		}
	return std::cout << f[1][n][201] << std::endl, 0;
}
posted @ 2020-10-07 18:48  epic01  阅读(313)  评论(0编辑  收藏  举报