CF147B. Smile House

有向图,求图上点数最小的正环的大小。

fd,i,jf_{d, i, j}:至多 2d2^{d} 边,从 iijj 的最大权值。

跳跃式倍增尝试使用 xx 条边,是否存在正环,即是否存在 gi,i>0g_{i, i}>0

CF1808E. Minibuses on Venus

题意:给定 N,KN,K,求满足存在 ii,使得 2aiai(modK)2 a_{i} \equiv \sum a_{i} \pmod K 的序列数量。

容斥,求对于任意的 ii,都满足 2aiS2a_i \neq S 的方案数。

枚举 SS,设 fi,jf_{i, j} 为前 ii 位,数字和是 jj 的方案数。

转移:fi,j+lfi1,lf_{i, j + l} \gets f_{i - 1, l},其中 2l≢S2l \not\equiv S

【快速幂】定义转移矩阵 TT,其中 Tj,j+l=[2lS]T_{j, j + l} = [2 l \neq S]。设 f0=[1,0,,0]f_{0}=[1, 0, \dots, 0],答案是 f0×Tnf_{0} \times T^n 的第 SS 项,复杂度 O(K4logN)\mathcal{O}(K^4\log N)

【卷积优化】定义转移多项式 gg,其中第 ll 项系数 gl=[2lS]g_{l} = [2 l \neq S],设 f0=1f_{0}=1,答案是 f0×gnf_{0} \times g^n,复杂度 O(K3logN)\mathcal{O}(K^{3}\log N)

CF351C. Jeff and Brackets

构造一个长度为 N×MN \times M 的合法括号序列。第 ii 个位置上的左括号代价为 AimodNA_{i\bmod N},右括号代价为 BimodNB_{i\bmod N}。求最小代价。(1N20,1M1071\leq N\leq 20, 1\leq M\leq 10^7

fi,jf_{i,j} 为到第 ii 的位置、有 jjjNj\leq N)个左括号未被匹配的最小代价。

{fi,j+1fi1,j+AimodNfi,j1fi1,j+BimodN\begin{cases} f_{i, j+1} \gets f_{i-1,j}+A_{i\bmod N} \\ f_{i, j-1} \gets f_{i-1,j}+B_{i\bmod N} \\ \end{cases}

其中 f0,0=0,f0,j=ef_{0,0}=0, f_{0,j}=e。其中 eemin\min 的幺元,即 ++\infty

Ti=[eAieBieAiBieAiBie](N+1)×(N+1)T_i = \begin{bmatrix} e & A_{i} & & \cdots & e \\ B_{i} & e & A_{i} & & \vdots \\ & B_{i} & e & A_{i} \\ \vdots & & B_{i} & \ddots \\ e & \cdots \\ \end{bmatrix}_{(N+1)\times (N+1)}

A0=(0,e,,e)A_0=(0, e, \dots, e),答案为 AMN=A0(T1T2TN)MA_{MN}= A_0 (T_{1}T_{2} \dots T_{N})^M 的第 0 项。

经典 DP

LPS(S)=LCS(S,reverse(S))\text{LPS}(S) = \text{LCS}(S, \text{reverse}(S))

  1. 给定一个长度为 NN 的字符串,为变成回文串,最少需要插入多少字符?
  2. 给定一个长度为 NN,有 KK 种类型的括号的字符串,为变成 RBS,最少需要插入多少字符?(注意区分 K=1K=1K2K \geqslant 2

本质不同的非空回文子序列数量

  • s[i]s[j]s[i] \neq s[j] 时,f[i][j]=f[i+1][j]+f[i][j1]f[i+1][j1]f[i][j] = f[i+1][j] + f[i][j-1] - f[i+1][j-1]
  • s[i]=s[j]s[i] = s[j]
    • 中间没有字符满足 s[x]=s[i]s[x]=s[i]f[i][j]=2×f[i+1][j1]+2f[i][j] = 2 \times f[i+1][j-1] + 2
    • 中间只有一个字符满足 s[x]=s[i]s[x]=s[i]f[i][j]=2×f[i+1][j1]+1f[i][j] = 2 \times f[i+1][j-1] + 1
    • 中间有两个或以上字符满足 s[x]=s[i]s[x]=s[i],记最左最右的分别为 l,rl,rf[i][j]=2×f[i+1][j1]f[l+1][r1]f[i][j] = 2 \times f[i+1][j-1] - f[l+1][r-1]

2150C

给定权值 VV 和排列 A,BA, B

设集合 UU。依次进行 NN 次操作,每次任选一种:

  • 找到当前的最小下标 ii 使得 AiSA_{i} \in S,将 AiA_{i}SS 中移除,将 AiA_{i} 加入 UU
  • 找到当前的最小下标 ii 使得 BiSB_i \in S,将 BiB_iSS 中移除。

求在所有可能的合法操作序列中,最大化 kUVk\sum_{k\in U}V_{k}

方法:DP

充要条件:对于任意一对满足 j<ij < i(即元素 AjA_j 在排列 AA 中出现在 AiA_i 的前面)且 BAj1>BAi1B^{-1}_{A_j} > B^{-1}_{A_i}(即元素 AjA_j 在排列 BB 中出现在 AiA_i 的后面)的下标对 (i,j)(i, j),如果 AiUA_i \in U,那么必须有 AjUA_j \in U

逆否命题:如果 AiUA_i \in U,且 Aj∉UA_j \not \in U,那么它们不能同时满足 j<ij < iBAj1>BAi1B^{-1}_{A_j} > B^{-1}_{A_i}

ii 从小到大进行决策,考虑所有满足 Aj∉UA_j \not \in Ujj,由于天然满足 j<ij < i,则不能满足 BAj1>BAi1B^{-1}_{A_j} > B^{-1}_{A_i},即必须 maxj:Aj∉UBAj1BAi1\max_{j:A_j \not \in U} B^{-1}_{A_j} \leqslant B^{-1}_{A_i}

考虑设 f(i,x)f(i, x) 表示已决策前 ii 个元素,且 x=maxj:Aj∉UBAj1x=\max_{j:A_j \not \in U} B^{-1}_{A_j} 时的最优解。

  • Ai∉UA_{i} \not\in Uf(i,max(x,BAj1))f(i1,x)f(i, \max(x, B^{-1}_{A_j})) \gets f(i - 1, x)
  • AiUA_{i} \in U,当 xBAi1x \leqslant B^{-1}_{A_i} 时,有 f(i,x)f(i1,x)+VAif(i, x) \gets f(i-1, x) + V_{A_i}

初始值:f(0,0)=0f(0, 0) = 0
答案:maxf(N,)\max f(N, *)

用线段树维护 f(x)f(x),转移即区间最值、单点修改、区间加。

1
2
3
4
5
6
7
8
9
10
11
std::vector<Info> init(N + 1);
init[0] = {0};
LazySegmentTree<Info, Lazy> seg(init);
for (int i = 0; i < N; i++) {
int p = invB[A[i]];
auto [M] = seg.query(0, p + 1);
seg.update(0, p + 1, {V[A[i]]});
auto [cur_p] = seg.query(p, p + 1);
seg.modify(p, {std::max(M, cur_p)});
}
auto [ans] = seg.query(0, N + 1);

方法:最大权闭合子图

充要条件:如果选择了 AiUA_i \in U,那么对于所有满足 j<ij < iBAj1>BAi1B^{-1}_{A_j} > B^{-1}_{A_i}jj,必须有 AjUA_j \in U

即选了 AiA_i,就必须选 AjA_j

  1. 所有 Vk>0V_k > 0 的点与源点 SS 相连,容量 VkV_k
  2. 所有 Vk<0V_k < 0 的点与汇点 TT 相连,容量 Vk|V_k|
  3. 如果选 ii 必须选 jj(即 j<ij < iBAj1>BAi1B^{-1}_{A_j} > B^{-1}_{A_i}),则从 iijj 连边,容量 ++\infty

最终的最大收益 =Vk>0Vk= \sum_{V_k > 0} V_k - 最小割,表示满足依赖关系,我们不得不放弃的正权值,或者被迫承受的负权值。

代码的遍历顺序是 ii00N1N-1,这意味着天然满足了 j<ij < i 的条件。当我们处理到第 ii 个元素时,之前遍历过的元素都是候选的 jj

遇到负权点时,相当于在图中增加了一个与汇点 TT 相连的节点。将其容量记录在 std::map 中,键为其在 BB 中的位置 p=BAi1p=B^{-1}_{A_i}。这些记录下来的负权值,就是后续正权点可以用来推流的目标。

遇到正权点时,首先将其乐观地全部加入 max_profit(即公式中的 Vk>0Vk\sum_{V_k > 0} V_k)。接下来,由于它可能依赖于前面遍历过的负权点,我们需要为其寻找能够割掉的代价。

根据条件 BAj1>BAi1B^{-1}_{A_j} > B^{-1}_{A_i},当前正权点只能流向位置大于 p=BAi1p=B^{-1}_{A_i} 的负权点。因此,使用 capacity.lower_bound(p) 查找所有满足条件的负权点。从满足的最小的 BAj1B^{-1}_{A_j} 开始,尝试用当前的 flow 去扣除负权点的容量,并同时从 max_profit 中减去这部分流出的代价。

填加乘

CF2028F. Alice’s Adventures in Addition

在序列中填加号或乘号,使运算结果恰好等于 mm1n2×105, 0ai104, 1m1041 \leqslant n\leqslant 2\times10^{5},\ 0 \leqslant a_{i} \leqslant 10^{4},\ 1 \leqslant m\leqslant 10^{4}

乘法的优先级高,设 dpi,xdp_{i,x} 表示处理到第 ii 个数,

  • 键:上一个加号之后的所有数乘积为 xx

  • 值:上一个加号之前能表示出的所有数的集合,用 bitset 维护。

转移分为 aia_{i} 前面填加号或乘号两种。具体见代码。

暴力转移的复杂度是对的,因为至多 20 个数乘起来就超过 10610^{6} 了,键不会很多,转移量在 logm\log m 级别,每次转移是 bitset 的复杂度 logm\log m。最终时间复杂度 Θ(nlog2m)\Theta(n\log^{2}m),空间复杂度 Θ(log2m)\Theta(\log^{2}m)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
constexpr int N = 1e4 + 8;
map<int, bitset<N>> dp;
dp[a[0]] = 1;
for (int i = 1; i < n; i++) {
map<int, bitset<N>> ndp;
for (auto& [x, mp] : dp) { // 乘号
// if (x * a[i] > m) continue; // WA6 直接跳过而不转移
ndp[min(m + 1, x * a[i])] |= mp;
}
for (auto& [x, mp] : dp) { // 加号
ndp[a[i]] |= mp << x;
}
dp = ndp;
}
bitset<N> res;
for (auto& [x, mp] : dp) {
res |= mp << x;
}
cout << (res[m] ? "YES" : "NO") << endl;

第十届中国大学生程序设计竞赛总决赛 G. + and × with a sugar / + , × 与糖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
int n;
cin >> n;
deque<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
int sum = 0;
while (!a.empty() && a.back() == 1) {
sum++;
a.pop_back();
}
while (!a.empty() && a.front() == 1) {
sum++;
a.pop_front();
}
bool flag = false;
ll res = 1;
for (int i = 0; i < a.size(); i++) {
res *= a[i];
res %= mod;
if (res > 2 * n) {
flag = true;
}
}
if (n == 0 || flag) {
cout << (res + sum) % mod << endl;
continue;
}
map<int, int> f;
f[a[0]] = 0;
for (int i = 1; i < a.size(); i++) {
map<int, int> nf;
for (auto& [k, v] : f) {
chmax(nf[k * a[i]], v);
chmax(nf[a[i]], v + k);
}
f = nf;
}
int ans = 0;
for (auto& [k, v] : f) {
chmax(ans, v + k);
}
cout << ans + sum << endl;