A.Spoon Taking Problem(模拟)
题意:
问题陈述
有 N N N人围坐在一张圆桌旁,按逆时针顺序编号为 1 1 1至 N N N。每个人都有一只惯用手:左手或右手。
圆桌上有 N N N把勺子,编号为 1 1 1至 N N N,每对相邻的人之间放一把勺子。在每个人 i i i( 1 ≤ i ≤ N 1\leq i\leq N 1≤i≤N)的左边和右边,分别有勺子 i i i和 ( i + 1 ) (i+1) (i+1)。这里,勺子 ( N + 1 ) (N+1) (N+1)指的是勺子 1 1 1。
下图是 N = 4 N=4 N=4的示意图。
给你一个 ( 1 , … , N ) (1,\dots,N) (1,…,N)的排列组合 ( P 1 , … , P N ) (P_1,\dots,P_N) (P1,…,PN)。在 i = 1 , … , N i=1,\dots,N i=1,…,N的顺序中,第 P i P_i Pi个人的行为如下:
- 如果左侧或右侧有剩余的勺子,他们将拿走其中一个。
- 如果两边都有剩余的勺子,他们会拿自己惯用手一边的勺子。
- 否则,他们什么也不会做。
我们还给出了一个长度为
N
N
N的字符串
S
S
S,由L
、R
和?
组成。在
2
N
2^N
2N种可能的惯用手组合中,求有多少种满足以下所有条件,答案对
998244353
998244353
998244353取模。
- 如果 S S S的 i i i个字符是"L",那么 i i i是左撇子。
- 如果 S S S的第 i i i个字符是"R",那么 i i i就是右撇子。
- 当每个人都表演完后,每个人都拿了一个勺子。
分析:
如果 s [ i ] = L s[i]=L s[i]=L,那么 i i i这个人就要拿左边的勺子,如果左边没有就拿右边的,右边也没有就无解。当 s [ i ] = R s[i]=R s[i]=R时同理。
如果 s [ i ] = ? s[i]=? s[i]=?,那么代表这个人的惯用手由我们决定。若第一个人拿了右边,那么接下来所有人都必须拿右边,否则一定无解。
考虑
s
[
i
]
=
?
s[i]=?
s[i]=?,如果有一开始拿的是右边,而且轮到?
的这个人,只有右边这个勺子,那么这个人是左手和右手都没有关系,总方案就会
∗
2
*2
∗2。
模拟所有情况即可。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL MOD = 998244353;
const LL N = 2e5 + 5;
int n, p[N];
string s;
int solveL() {
LL ans = 1;
bool vis[n + 5];
for (int i = 0; i <= n + 1; i++)
vis[i] = false;
vis[0] = vis[n + 1] = true;
vis[p[1]] = true;
for (int i = 2; i <= n; i++) {
int l = p[i];
int r = p[i] + 1;
if (r == n + 1)
r = 1;
if (vis[l] && vis[r]) {
return 0;
} else if (!vis[l] && !vis[r]) {
if (s[l] == 'R') {
return 0;
}
vis[l] = true;
} else if (!vis[l]) {
vis[l] = true;
if (s[l] == '?') {
ans *= 2;
ans %= MOD;
}
} else {
return 0;
}
}
return ans;
}
int solveR() {
LL ans = 1;
bool vis[n + 5];
for (int i = 0; i <= n + 1; i++)
vis[i] = false;
vis[0] = vis[n + 1] = true;
if (p[1] + 1 == n + 1)
vis[1] = true;
else
vis[p[1] + 1] = true;
for (int i = 2; i <= n; i++) {
int l = p[i], r = p[i] + 1;
if (r == n + 1)
r = 1;
if (vis[l] and vis[r]) {
return 0;
} else if (!vis[l] and !vis[r]) {
if (s[l] == 'L') {
return 0;
}
vis[r] = true;
} else if (!vis[r]) {
vis[r] = true;
if (s[l] == '?') {
ans *= 2;
ans %= MOD;
}
} else {
return 0;
}
}
return ans;
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++)
cin >> p[i];
cin >> s;
s = "0" + s;
LL ans;
if (s[p[1]] == 'L')
ans = solveL();
else if (s[p[1]] == 'R')
ans = solveR();
else
ans = solveL() + solveR();
cout << ans % MOD << endl;
return 0;
}
B.Parenthesis Arrangement(栈)
题意:
问题陈述
给你一个长度为
2
N
2N
2N的字符串
S
S
S,由字符(
和)
组成。让
S
i
S_i
Si表示
S
S
S左边的第
i
i
i个字符。
你可以按任意顺序执行以下两种类型的操作零次或多次:
-
选择一对整数 ( i , j ) (i,j) (i,j),使得 1 ≤ i < j ≤ 2 N 1\leq i\lt j\leq 2N 1≤i<j≤2N。交换 S i S_i Si和 S j S_j Sj。这一操作的代价是 A A A。
-
选择一个整数 i i i,使得 1 ≤ i ≤ 2 N 1\leq i\leq 2N 1≤i≤2N。将 S i S_i Si替换为
(
或)
。这个操作的代价是 B B B。
你的目标是使 S S S成为一个正确的括号序列。求实现这一目标所需的最小总成本。可以证明,用有限次的运算总能达到目标。
什么是正确的括号序列。正确的括号序列是满足以下任意条件的字符串:
- 它是空字符串。
- 它是由
(
, A A A,)
按此顺序连接而成,其中 A A A是一个正确的括号序列。 - 由 A A A和 B B B依次连接而成,其中 A A A和 B B B是正确的括号序列。
分析:
由题意得合法的括号不需要修改。
建立一个栈用于遍历括号序列,进栈,如果遇到栈顶是(
,当前是)
,弹出栈顶,否则当前入栈
然后就会剩下
- (((((
- ))))))
- ))((((
这三种情况。
显然 1 1 1和 2 2 2只能修改一半使序列合法。 3 3 3交换一次相当于修改两个位置。如果 a < = 2 ∗ b a<=2*b a<=2∗b,一定要先执行交换操作。
模拟一下若有))((((
,交换一次()((()
,发现一次交换产生了两对合法括号。
交换完毕之后如果还有剩下的((((
,那么只需要将一半翻转。
如果
a
>
2
∗
b
a>2*b
a>2∗b,那么我们只能翻转。)))(((
,左半边翻转一半,右半边翻转一半,如果是奇数还剩下两个括号)(
,那么还需要再翻两遍。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
void solve() {
LL n, a, b;
cin >> n >> a >> b;
string s;
cin >> s;
stack<char> s1, s2;
s1.push(s[0]);
for (int i = 1; i <= s.length() - 1; i++) {
if (s1.size() && s1.top() == '(' && s[i] == ')') {
s1.pop();
} else {
s1.push(s[i]);
}
}
LL c9 = 0, c0 = 0;
while (s1.size()) {
if (s1.top() == '(')
c9++;
else
c0++;
s2.push(s1.top());
s1.pop();
}
LL ans = 0;
if (c9 == 0 || c0 == 0) {
ans = (c9 + c0) / 2 * b;
} else {
if (a <= 2 * b) {
ans = (min(c0, c9) + 1) / 2 * a + (max(c0, c9) - min(c0, c9)) / 2 * b;
} else {
if (c0 & 1) {
ans = (c0 / 2 + c9 / 2 + 2) * b;
} else {
ans = (c0 / 2 + c9 / 2) * b;
}
}
}
cout << ans << endl;
}
int main() {
solve();
return 0;
}
C.Jumping Through Intervals(数学)
题意:
问题陈述
给你 N N N对整数 ( L 1 , R 1 ) , ( L 2 , R 2 ) , … , ( L N , R N ) (L_1,R_1),(L_2,R_2),\dots,(L_N,R_N) (L1,R1),(L2,R2),…,(LN,RN)。这里, L i ≤ R i L_i\leq R_i Li≤Ri代表所有的 1 ≤ i ≤ N 1\leq i\leq N 1≤i≤N。
由 N N N个整数组成的序列 A = ( A 1 , A 2 , … , A N ) A=(A_1,A_2,\ldots,A_N) A=(A1,A2,…,AN)如果满足以下条件,则称为好整数序列:
- 所有 1 ≤ i ≤ N 1\leq i\leq N 1≤i≤N均为 L i ≤ A i ≤ R i L_i\leq A_i\leq R_i Li≤Ai≤Ri。
求使 ∑ i = 1 N − 1 ∣ A i + 1 − A i ∣ \displaystyle\sum_{i=1}^{N-1}|A_{i+1}-A_{i}| i=1∑N−1∣Ai+1−Ai∣最小的的字典序最小的好整数序列 A A A。
分析:
首先,对于 1 ≤ k ≤ N 1\leq k\leq N 1≤k≤N 和整数 x x x ,我们定义 f k ( x ) f_k(x) fk(x) 如下:
f k ( x ) = ( The minimum value of ∑ i = k N − 1 ∣ A i + 1 − A i ∣ under the constraints A k = x , L i ≤ A i ≤ R i ( k + 1 ≤ i ≤ N ) ) f_k(x)=\Big(\text{The minimum value of}\sum_{i=k}^{N-1}|A_{i+1}-A_i| \text{under the constraints}A_k=x,L_i \leq A_i \leq R_i\ (k+1\leq i\leq N)\Big) fk(x)=(The minimum value ofi=k∑N−1∣Ai+1−Ai∣under the constraintsAk=x,Li≤Ai≤Ri (k+1≤i≤N))
从定义可知, f N ( x ) = 0 f_N(x)=0 fN(x)=0。另外,我们对整数集合 I k I_k Ik定义如下:
I k = a r g m i n x ∈ [ L k , R k ] ∩ Z f k ( x ) = { x ∈ [ L k , R k ] ∩ Z ∣ f k ( x ) = min x ′ ∈ [ L k , R k ] ∩ Z f k ( x ′ ) } . \begin{aligned} I_k = \underset{x\in [L_k, R_k] \cap \mathbb{Z}}{\mathrm{argmin }} f_k(x) \\ & = \Big\lbrace x \in [L_k, R_k] \cap \mathbb{Z} \Bigm\vert f_k(x) = \min_{x'\in [L_k, R_k] \cap \mathbb{Z}} f_k(x') \Big\rbrace. \end{aligned} Ik=x∈[Lk,Rk]∩Zargminfk(x)={x∈[Lk,Rk]∩Z fk(x)=x′∈[Lk,Rk]∩Zminfk(x′)}.
那么,下面的命题成立。
命题 1
I k I_k Ik可以表示为 1 ≤ k ≤ N 1\leq k\leq N 1≤k≤N的整数间隔。也就是说,存在整数 l k , r k ( l k ≤ r k ) l_k,r_k\ (l_k\leq r_k) lk,rk (lk≤rk)这样的 I k = [ l k , r k ] ∩ Z I_k=[l_k,r_k]\cap\mathbb{Z} Ik=[lk,rk]∩Z。以下我们使用这些 l k , r k l_k,r_k lk,rk。
2. I N = [ L N , R N ] I_N=[L_N,R_N] IN=[LN,RN],而对于 k < N k\lt N k<N、
- [ L k , R k ] ∩ I k + 1 ≠ ∅ [L_k,R_k]\cap I_{k+1}\neq\emptyset [Lk,Rk]∩Ik+1=∅,则 I k = [ L k , R k ] ∩ I k + 1 I_k=[L_k,R_k]\cap I_{k+1} Ik=[Lk,Rk]∩Ik+1。
- I k = { R k } I_k= \lbrace R_k\rbrace Ik={Rk},若 R k < l k + 1 R_k\lt l_{k+1} Rk<lk+1。
- I k = { L k } I_k= \lbrace L_k\rbrace Ik={Lk}若 L k > r k + 1 L_k\gt r_{k+1} Lk>rk+1。
让 m k = min x ∈ [ L k , R k ] ∩ Z f k ( x ) m_k=\displaystyle\min_{x\in[L_k,R_k]\cap\mathbb{Z}}f_k(x) mk=x∈[Lk,Rk]∩Zminfk(x)。对于 1 ≤ k < N 1\leq k\lt N 1≤k<N、f k ( x ) = { m k + 1 + l k − x ( x < l k ) m k + 1 ( x ∈ [ l k , r k ] ) m k + 1 + x − r k ( r k < x ) f_k(x)=\begin{cases}m_{k+1}+l_k-x&(x\lt l_k)\\m_{k+1}& (x\in[l_k,r_k])\\m_{k+1}+x-r_k&(r_k\lt x)\end{cases} fk(x)=⎩ ⎨ ⎧mk+1+lk−xmk+1mk+1+x−rk(x<lk)(x∈[lk,rk])(rk<x)
这个命题可以用归纳法按 k k k的降序证明。根据命题1,存在整数 l k , r k l_k,r_k lk,rk,使得 I k = [ l k , r k ] I_k=[l_k,r_k] Ik=[lk,rk]。同时,根据命题1.2,我们可以确定每个 l k , r k l_k,r_k lk,rk按 k k k的降序排列。
接下来,我们考虑构建词性最小解。根据 I 1 I_1 I1的定义,设置 A 1 = min I 1 = l 1 A_1=\min I_1=l_1 A1=minI1=l1即可。接下来,对于 k ≥ 2 k\geq 2 k≥2,当 A 1 , … , A k − 1 A_1,\dots,A_{k-1} A1,…,Ak−1确定后,我们考虑确定 A k A_k Ak。这里,在能使 ∣ x − A k − 1 ∣ + f k ( x ) |x-A_{k-1}|+f_k(x) ∣x−Ak−1∣+fk(x)最小化的 x ∈ [ L k , R k ] x\in[L_k,R_k] x∈[Lk,Rk]中,应选择最小的 A k A_k Ak。利用命题1.3进行计算,可以得出 A k = max { min { A k − 1 , r k } , L k } A_k=\max\lbrace\min\lbrace A_{k-1},r_k\rbrace,L_k\rbrace Ak=max{min{Ak−1,rk},Lk}。因此,我们可以按照 k k k的升序确定每个 A k A_k Ak。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
int main() {
int n;
cin >> n;
vector<LL> L(n), R(n);
for (int i = 0; i < n; i++) cin >> L[i] >> R[i];
vector<LL> l(n), r(n);
l.back() = L.back(), r.back() = R.back();
for (int i = n - 2; i >= 0; i--) {
if (R[i] < l[i + 1]) {
l[i] = r[i] = R[i];
} else if (L[i] > r[i + 1]) {
l[i] = r[i] = L[i];
} else {
l[i] = max(L[i], l[i + 1]);
r[i] = min(R[i], r[i + 1]);
}
}
vector<LL> a(n);
a.front() = l.front();
for (int i = 1; i < n; i++) {
a[i] = std::clamp(a[i - 1], L[i], r[i]);
}
for (int i = 0; i < n; i++) {
cout << a[i] << (i == n - 1 ? '\n' : ' ');
}
return 0;
}
D.LIS on Tree 2(LIS)
题意:
问题陈述
有一棵树,树上有 N N N个顶点,编号为 1 1 1至 N N N。树的第 i i i条边双向连接顶点 u i u_i ui和 v i v_i vi。
对于 ( 1 , … , N ) (1,\ldots,N) (1,…,N)的排列 P = ( P 1 , … , P N ) P=(P_1,\ldots,P_N) P=(P1,…,PN),我们定义 f ( P ) f(P) f(P)如下:
- 对于每个顶点 i ( 1 ≤ i ≤ N ) i \ (1\leq i\leq N) i (1≤i≤N),假设 ( v 1 = 1 , v 2 , … , v k = i ) (v_1=1,v_2,\ldots,v_k=i) (v1=1,v2,…,vk=i)是顶点 1 1 1到顶点 i i i的简单路径,假设 L i L_i Li是 ( P v 1 , P v 2 , … , P v k ) (P_{v_1},P_{v_2},\ldots,P_{v_k}) (Pv1,Pv2,…,Pvk)的最长递增子序列的长度。我们定义 f ( P ) = ∑ i = 1 N L i f(P)=\sum\limits_{i=1}^N L_i f(P)=i=1∑NLi。
给你一个整数 K K K。请判断是否存在 ( 1 , … , N ) (1,\ldots,N) (1,…,N)的排列 P P P使得 f ( P ) = K f(P)=K f(P)=K。如果存在,请给出一个这样的排列。
分析:
首先,判断什么情况无解。记 s i s_i si为以 i i i为根的子树大小, p i p_i pi为 i i i的深度( d 1 = 1 d_1=1 d1=1),显然有 k < n k \lt n k<n或 k ≤ ∑ p i k \le \sum p_i k≤∑pi时无解(因为有 1 ≤ L i ≤ d i 1≤L_i≤d_i 1≤Li≤di)。以下将通过构造说明其他情况( k ∈ [ n , ∑ p i ] k∈[n,\sum p_i] k∈[n,∑pi])都有解。
更改点 i i i的 p i p_i pi会造成很多点的 L j L_j Lj发生变化,所以这不太好考虑。
我们直接假设最终构造中每个点路径上的 L I S LIS LIS一定是它父节点的 L I S LIS LIS拼上(或不拼上)自己。这样如果 i i i在 1 1 1到 i i i的路径的 L I S LIS LIS上, i i i一定也在子树中所有点的 L I S LIS LIS上,这个点就会对答案 f ( p ) f(p) f(p)造成 s i s_i si的贡献;否则就造成 0 0 0的贡献。
发现这变成了一个背包问题:我们需要选择若干个节点( 1 1 1是必选)使得它们的贡献 s i s_i si加起来恰好为 k k k。
注意到如果我们把 s i s_i si按从大到小排序,就有 s i ≤ ∑ s j + 1 ( j > i ) s_i≤\sum s_j +1 (j>i) si≤∑sj+1(j>i)(因为一个点的子树大小就是它所有的儿子的子树大小和 + 1 +1 +1,那这个点最大也不过是比它小的所有点都是它的儿子),因此我们从大到小贪心,每次遇到 k ≥ s i k≥s_i k≥si就可以从 k k k中减掉 s i s_i si,剩下的部分一定可以在后面拼出。
好了,我们已经决定哪些点要造成贡献了,答案该怎么构造呢?
为了保证所有没取到的点一定不造成贡献,我们把所有没取到的点的权值设为比取到的点的权值小,这样就不会拼在已经形成的 L I S LIS LIS的后面;为了保证没取到的点不会自己形成一段 L I S LIS LIS,我们只要让所有没取到的点的权值都从根往下降序排列就行。同理,既然取到的点要形成 L I S LIS LIS,那么这些点的权值就应该从根往下升序排列。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL N = 2e5 + 5;
LL k;
int sz[N];
vector<int> T[N];
pair<int, int> p[N];
int n;
void dfs(int x, int fa) {
sz[x] = 1;
for (int v: T[x]) {
if (v != fa) {
dfs(v, x);
sz[x] += sz[v];
}
}
p[x] = {-sz[x], x};
}
int vis[N], ans[N];
int tot = 0;
void dfs0(int x, int fa) {
for (int v: T[x])
if (v != fa)
dfs0(v, x);
if (!vis[x])
ans[x] = ++tot;
}
void dfs1(int x, int fa) {
if (vis[x])
ans[x] = ++tot;
for (int v: T[x])
if (v != fa)
dfs1(v, x);
}
int main() {
cin >> n >> k;
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
T[u].push_back(v);
T[v].push_back(u);
}
dfs(1, 0);
LL sum = 0;
for (int i = 1; i <= n; i++)
sum += sz[i];
if (k < n || k > sum) {
cout << "No";
return 0;
}
cout << "Yes" << endl;
sort(p + 1, p + n + 1);
for (int i = 1; i <= n; i++) {
if (k + p[i].first >= 0) {
vis[p[i].second] = 1;
k += p[i].first;
}
}
dfs0(1, 0);
dfs1(1, 0);
for (int i = 1; i <= n; i++)
cout << ans[i] << " ";
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。