- CF 358D Dima and Hares
- B. Catching Cheaters">CF 683 B. Catching Cheaters
- D. Easy Problem">CF Educational 57 D. Easy Problem
- CF 1716 D. Chip Move
CF 358D Dima and Hares
N个物品排成一排,按照一定顺序将所有物品都拿走,如果拿走某个物品时相邻两个物品都没有被拿过,那么得到的价值为ai;如果相邻的两个物品有一个被拿过(左右无所谓),那么得到的价值为bi;如果相邻的两个物品都被拿走了,那么对应价值为ci。问能够获得的最高价值为多少。
CF 683 B. Catching Cheaters
背景:最长公共子序列(LCS) https://leetcode-cn.com/problems/longest-common-subsequence/
给你 n,m(<=5000),长为 n 的字符串 s,长为 m 的字符串 t。定义 LCS(x,y) 表示字符串 x 和 y 的最长公共子序列。
请你选择 s 的子串 s’,t 的子串 t’,最大化 4*len(LCS(s’,t’))-len(s’)-len(t’) 的值。
输出这个最大值。
输入的字符串只包含小写字母。
子串是连续的,子序列不要求连续。
思路:
LCS的变种
设x = len(lcs(s', t')), a = len(s'), b = len(t')
即max(x - (a - x) + x - (b - x)
对于一段s' t'
来讲,属于lcs部分的贡献为2,否则贡献为-1
故在原LCS的基础上做修改即可
static String s, t;
static void solve() {
int n = ni(), m = ni();
s = ns();
t = ns();
int[][] f = new int[n + 1][m + 1];
int ans = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
f[i][j] = Math.max(0, Math.max(f[i - 1][j] - 1, f[i][j - 1] - 1));
if (s.charAt(i - 1) == t.charAt(j - 1))
f[i][j] = Math.max(f[i][j], f[i - 1][j - 1] + 2);
ans = Math.max(ans, f[i][j]);
}
}
out.println(ans);
}
CF Educational 57 D. Easy Problem
给你一个 n(<=1e5),一个长为 n 的字符串 s 和一个长为 n 的数组 a(1<=a[i]<=998244353)。
表示每个 s[i] 都有一个对应的删除代价 a[i]。
请你删除 s 中的某些字符,使得 s 不包含 “hard” 子序列。
输出被删除字母的代价之和的最小值。
子序列不要求连续。s 仅包含小写字母。
思路:
DP,难点在于如何进行状态表示
首先考虑单个字符的情况,再依次考虑多个字符的情况
若题意为”请你删除 s 中的某些字符,使得 s 不包含 “h” 子序列”,删除所有的'h'
即可
若题意为”请你删除 s 中的某些字符,使得 s 不包含 “ha” 子序列”,考虑当前字符如果是'a'
,需保证要么前面没有'h'
出现,要么删除当前'a'
…
故可考虑状态表示为f[i][j]
表示考虑前i
个字符,不包含"hard"[:j)
子序列的所有删除方案。
属性是最小删除代价
状态转移:s[i] == c[j], f[i][j] = min(f[i - 1][j - 1], f[i - 1][j] + a[i]
s[i] != c[j], f[i][j] = f[i - 1][j]
static final int N = 100010;
static String s;
static int[] a = new int[N];
static int n;
static long[][] f = new long[N][5];
static void solve() {
n = ni();
s = " " + ns();
for (int i = 1; i <= n; i++)
a[i] = ni();
char[] ch = {' ', 'h', 'a', 'r', 'd'};
for (int i = 0; i <= n; i++)
f[i][0] = (long)(1e18);
for (int i = 1; i <= n; i++) {
char c = s.charAt(i);
for (int j = 1; j <= 4; j++) {
if (c != ch[j])
f[i][j] = f[i - 1][j];
else
f[i][j] = Math.min(f[i - 1][j - 1], f[i - 1][j] + a[i]);
}
}
out.println(f[n][4]);
}
CF 1716 D. Chip Move
给定n
和k
,初始位于0处,第一次移动步长为k
的整数倍,第x
次移动步长为k + x - 1
的整数倍
不限移动次数,问从0移动到[1, n]
的方案数各为多少?
数据范围:n, k ∈ [1, 200000]
思路:
DP
极限情况n = 20000, k = 1
从0开始,最多不超过700步可达n
。
故可使用滚动数组,考虑每一步的情况,再利用前缀和加速计算tmp[i]
表示当前步数情况下到i
的方案数pre[i]
表示上一轮到i
的方案数res[i]
记录最终结果
static final int N = 200010, MOD = 998244353, M = 700;
static int[] tmp = new int[N], pre = new int[N], res = new int[N];
static int n, k;
static void solve() {
n = ni();
k = ni();
// 一开始位于0处,方案数为1
pre[0] = 1;
// 模拟每一轮
for (int t = k, c = 1; t <= n && c <= M; t++, c++) {
// 枚举可能的起点
for (int st = 0; st < t; st++) {
// 前缀和加速计算
int s = 0;
for (int i = st; i <= n; i += t) {
// 计算当前轮
tmp[i] += s;
if (tmp[i] >= MOD) tmp[i] -= MOD;
// 累计方案数
s += pre[i];
if (s >= MOD) s -= MOD;
}
}
boolean flag = false;
for (int i = 0; i <= n; i++) {
pre[i] = tmp[i];
res[i] += tmp[i];
if (res[i] >= MOD) res[i] -= MOD;
tmp[i] = 0;
if (pre[i] > 0) flag = true;
}
if (!flag) break;
}
for (int i = 1; i <= n; i++)
out.print(res[i] + " ");
}