归档 221018 - 221021 | 做题记录

很难评价我的做题记录、勇哥的笔记和 coolcool 村村长的视频哪个更意识流。

K. Difference - 221018

https://loj.ac/p/2161

好耶我会打 \(n^3\)!这说明这道题肯定是一个我不会写的 \(n^2\)

\(n\)\(10^6\)????寄……


枚举出现次数最多的字符(假设为 \(x\))和出现次数最少的字符(假设为 \(y\))。

然后用一个类似于双指针的东西,额,我解释不大来,总之意思就是我们要让 \(y\)\(x\) 第一次出现的地方分别成为起始区间的起点和终点。

说是起点和终点,但实际上你不用特别在意它们一开始的前后顺序,因为我们会把 \(26\times 26\) 中情况全部枚举一遍。。。而且不排除存在 \(x\) 反杀的可能性。

然后,如果右端点能往右挪,多塞下一个 \(x\),我就肯定往右挪,此时统计的答案增加 \(1\)。如果 \(x\) 挪不了了,那就往右挪 \(y\),不停下来是因为虽然 \(x\) 现在一时吃瘪,但后面还是有可能反杀 \(y\)

那么问题来了,你怎么知道你枚举的这个区间里 \(x\) 一定出现次数最多,\(y\) 一定出现次数最少???

我可没这么说过啊,思考,如果 \(x\) 出现次数不是最多,\(y\) 出现次数不是最少,说明答案也不是这个区间里最多的,所以后面还会被更新。

关于「往右挪」这个操作,我们预处理一下某个字符某一次出现的位置就好。

然后注意跳的时候如果出现了数量统计为负的情况就可以从 0 开始统计了!

然后最后的时间复杂度是 \(\mathcal O(n)\),因为要非常严谨地 KA 掉常数,虽然这个常数是一个明显跑不满的 \(10^2\) 级别。。。

namespace XSC062 {
using namespace fastIO;
const int maxm = 28;
const int maxn = 1e6 + 5;
char s[maxn];
int cnt[maxm];
int d[maxm][maxn];
int n, l, r, res, ans, t, f;
inline int max(int x, int y) {
	return x > y ? x : y; 
}
int main() {
	read(n);
	reads(s + 1);
	for (int i = 1; i <= n; ++i)
		d[s[i] - 'a' + 1][++cnt[s[i] - 'a' + 1]] = i;
	for (int x = 1; x <= 26; ++x) {
		if (!cnt[x])
			continue;
		for (int y = 1; y <= 26; ++y) {
			if (!cnt[y] || x == y)
				continue;
			f = 0;
			t = cnt[x] + cnt[y];
			l = 1, r = 1, res = -1;
			while (t--) {
				if ((l <= cnt[x] && d[x][l] < d[y][r])
					|| r > cnt[y])
					++l, ++res;
				else ++r, res -= f, f = 1;
				if (res < 0)
					f = 0;
				ans = max(ans, res);
			}
		}
	}
	print(ans);
	return 0;
}
} // namespace XSC062

L. 前缀单词 - 221019

https://www.luogu.com.cn/problem/P1666

智力过来问我:有什么办法可以在一个图里找到一些边,满足它们没有公共端点?

我:……

我:……一般图最大匹配?

后来反应过来智力问的是这道题。不过他倒是给了我一个思路,可以把它转换成连边的形式来做。

先摆一会儿。


C. 金字塔 - 221021

这道题是不是很简单啊,怎么感觉除了我所有人都会做。。。

https://www.acwing.com/problem/content/286/

就是给了一个欧拉序,求可能的树的形状数量。

那它为什么是区间 DP 呢?我不知道…… 如果它不放在区间 DP 的链接里面我肯定是看不出来的……

因为从来就没有弄懂过这道题,今天决定完全自己想一次。

首先,因为是区间 DP,所以 f[i][j] 表示的应该是 \(s_i\sim s_j\) 组成树的方案数。

此时 \(s_i\) 一定等于 \(s_j\)

那么怎么转移呢?不妨先从简单的情况看起。

\(s_i\sim s_j\) 中,根节点只包含一个子树,那么方案数就是 f[i + 1][j - 1]

如果包含两个,在中间寻找一个点 \(k\) 使得 \(s_i=s_k\),方案数为 f[i + 1][k - 1] * f[k + 1][j]

如果包含三个,在中间寻找点 \(k_1\)\(k_2\),使得 \(k_1 < k_2\)\(s_i=s_{k_1}=s_{k_2}\),方案数为 f[i + 1][k1 - 1] * f[k1 + 1][k2 - 1] * f[k2 + 1][j - 1]

我们发现到这个时候式子就已经很复杂了。但注意到 \(s_i=s_{k_1}=s_{k_2}\),而 f[i][j] 满足 \(s_i = s_j\),不难想到将 f[i + 1][k1 - 1] 转化为 f[i][k1],将其视为树。

但如果直接转移会有重复情况。

这个时候我们选择,f[i][j] = f[i][k] * f[k + 1][j - 1],将右边那一坨视为独立的、不以 \(s_i\) 为根的树。

namespace XSC062 {
using namespace fastIO;
const int mod = 1e9;
const int maxn = 305;
int n;
char a[maxn]; 
int f[maxn][maxn];
int main() {
	reads(a + 1);
	n = strlen(a + 1);
	for (int i = 1; i <= n; ++i)
		f[i][i] = 1;
	for (int l = 2; l <= n; ++l) {
		for (int i = 1; i <= n - l + 1; ++i) {
			int j = i + l - 1;
			if (a[i] != a[j])
				continue;
			for (int k = i; k < j - 1; ++k) {
				if (a[k] != a[i])
					continue;
				(f[i][j] += f[i][k]
						* f[k + 1][j - 1] % mod) %= mod;
			}
		}
	}
	print(f[1][n]);
	return 0;
}
} // namespace XSC062

D. 2-05F. zzj & liaoy の 毁灭打击

不难发现(假的,其实想了很久),如果我们要更新 \(i\sim j\) 的答案,会受到相邻区间的影响。不妨设 f[i][j] 为把 \(i\sim j\) 内全部删除,只剩 \(i\)\(j\) 的最大收益,因为 \(i\)\(j\) 必须保留,有 f[i][j] = max {f[i][k] + f[k][j] + a[i] * a[j]}

namespace XSC062 {
using namespace fastIO;
const int maxn = 303;
int n;
int a[maxn];
int f[maxn][maxn], p[maxn][2];
inline int max(int x, int y) {
	return x > y ? x : y;
}
int main() {
	read(n);
	a[1] = a[n + 2] = 1;
	for (int i = 2; i <= n + 1; ++i)
		read(a[i]);
	n += 2;
	for (int l = 3; l <= n; ++l) {
		for (int i = 1; i <= n - l + 1; ++i) {
			int j = i + l - 1;
			for (int k = i + 1; k < j; ++k)
				f[i][j] = max(f[i][j], f[i][k]
						+ f[k][j] + a[i] * a[j]);
		}
	}
	print(f[1][n]);
	return 0;
}
} // namespace XSC062

E. 基因串 - 221021

https://loj.ac/p/2889

题目叫我优化掉 \(26^2\) 的系数,但我就不 😤(理直气壮)

可是很明显唯一 KA 掉了 \(26^2\) 的代码(link)是神啊,我看不懂 🙃

注意到询问的长度上限是 \(100\)。对于每一次询问,我们定义 f[i][j][k] 表示 \(i\sim j\) 是否能缩成字母 \(k\)

然后就很显而易见了,跑一个区间 DP,然后再来另一个区间 DP,计算 \(i\sim j\) 会缩成多少个 S。

namespace XSC062 {
using namespace fastIO;
const int maxm = 35;
const int maxn = 105;
const int inf = 0x3f3f3f3f;
int x, y, z;
int s[maxn];
char a, b, c;
char t[maxn];
int n, m, len;
int dp[maxn][maxn];
int cnt[maxm][maxm];
bool f[maxn][maxn][maxm];
int rul[maxm][maxm][maxn];
inline int min(int x, int y) {
	return x < y ? x : y;
}
int main() {
	read(n);
	for (int i = 1; i <= n; ++i) {
		readc(c), readc(a), readc(b);
		x = a - 'A' + 1;
		y = b - 'A' + 1;
		z = c - 'A' + 1;
		rul[x][y][++cnt[x][y]] = z;
	}
	read(m);
	while (m--) {
		reads(t + 1);
		len = strlen(t + 1);
		memset(f, 0, sizeof (f));
		for (int i = 1; i <= len; ++i) {
			s[i] = t[i] - 'A' + 1;
			f[i][i][s[i]] = 1;
			if (i == 1)
				continue;
			for (int j = 1; j <= cnt[s[i - 1]][s[i]]; ++j)
				f[i - 1][i][rul[s[i - 1]][s[i]][j]] = 1;
		}
		for (int l = 3; l <= len; ++l) {
			for (int i = 1; i <= len - l + 1; ++i) {
				int j = i + l - 1;
				for (int k = i; k < j; ++k) {
					for (int k1 = 1; k1 <= 26; ++k1) {
						if (!f[i][k][k1])
							continue;
						for (int k2 = 1; k2 <= 26; ++k2) {
							if (!f[k + 1][j][k2])
								continue;
							for (int k3 = 1; k3 <= cnt[k1][k2];
											 ++k3)
							 	f[i][j][rul[k1][k2][k3]] = 1;
						}
					}
				}
			}
		}
		for (int i = 1; i <= len; ++i) {
			for (int j = i; j <= len; ++j)
				dp[i][j] = f[i][j]['S' - 'A' + 1] ? 1 : inf;
		}
		for (int l = 3; l <= len; ++l) {
			for (int i = 1; i <= len - l + 1; ++i) {
				int j = i + l - 1;
				for (int k = i; k < j; ++k) {
					dp[i][j] = min(dp[i][j], dp[i][k]
											 + dp[k + 1][j]);
				}
			}
		}
		if (dp[1][len] == inf)
			puts("NIE");
		else print(dp[1][len], '\n');
	}
	return 0;
}
} // namespace XSC062

F. 马棚问题 - 221021

首先我们跑一个区间 DP,算出把 \(i\sim j\) 放到一个马棚的代价。

然后再跑一个线性 DP,得到答案。

namespace XSC062 {
using namespace fastIO;
const int maxn = 505;
const int inf = 0x3f3f3f3f;
int n, k;
int a[maxn];
int f[maxn][maxn], dp[maxn][maxn];
inline int min(int x, int y) {
	return x < y ? x : y;
}
int main() {
#ifdef ONLINE_JUDGE
	freopen("horse.in", "r", stdin);
	freopen("horse.out", "w", stdout);
#endif
	read(n), read(k);
	for (int i = 1; i <= n; ++i)
		read(a[i]);
	for (int l = 1; l <= n; ++l) {
		for (int i = 1; i <= n - l + 1; ++i) {
			int j = i + l - 1, t0 = 0, t1 = 0;
			for (int k = i; k <= j; ++k) {
				if (a[k] == 0)
					++t0;
				else ++t1;
			}
			f[i][j] = t0 * t1;
		}
	}
	memset(dp, 0x3f, sizeof (dp));
	dp[0][0] = 0;
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= k; ++j) {
			for (int k = 0; k < i; ++k) {
				dp[i][j] = min(dp[i][j], dp[k][j - 1]
										 + f[k + 1][i]);
			}
		}
	}
	print(dp[n][k]);
	return 0;
}
} // namespace XSC062
posted @ 2022-10-18 21:37  XSC062  阅读(43)  评论(0编辑  收藏  举报