1. 程式人生 > 實用技巧 >AtCoder Regular Contest 104 題解

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;
}