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\)
直接分段 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;
}