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-T
,C-G
)。
Solution
题目条件即为子区间内 A
和 T
个数相等,C
和 G
相等,直接枚举子区间,前缀和判断即可,\(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;
}