A.Spoon Taking Problem(模拟)
题意:
问题陈述
有NNN人围坐在一张圆桌旁,按逆时针顺序编号为111至NNN。每个人都有一只惯用手:左手或右手。
圆桌上有NNN把勺子,编号为111至NNN,每对相邻的人之间放一把勺子。在每个人iii(1≤i≤N1\leq i\leq N1≤i≤N)的左边和右边,分别有勺子iii和(i+1)(i+1)(i+1)。这里,勺子(N+1)(N+1)(N+1)指的是勺子111。
下图是N=4N=4N=4的示意图。

给你一个(1,…,N)(1,\dots,N)(1,…,N)的排列组合(P1,…,PN)(P_1,\dots,P_N)(P1,…,PN)。在i=1,…,Ni=1,\dots,Ni=1,…,N的顺序中,第PiP_iPi个人的行为如下:
- 如果左侧或右侧有剩余的勺子,他们将拿走其中一个。
- 如果两边都有剩余的勺子,他们会拿自己惯用手一边的勺子。
- 否则,他们什么也不会做。
我们还给出了一个长度为NNN的字符串SSS,由L、R和?组成。在2N2^N2N种可能的惯用手组合中,求有多少种满足以下所有条件,答案对998244353998244353998244353取模。
- 如果SSS的iii个字符是"L",那么iii是左撇子。
- 如果SSS的第iii个字符是"R",那么iii就是右撇子。
- 当每个人都表演完后,每个人都拿了一个勺子。
分析:
如果s[i]=Ls[i]=Ls[i]=L,那么iii这个人就要拿左边的勺子,如果左边没有就拿右边的,右边也没有就无解。当s[i]=Rs[i]=Rs[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(栈)
题意:
问题陈述
给你一个长度为2N2N2N的字符串SSS,由字符(和)组成。让SiS_iSi表示SSS左边的第iii个字符。
你可以按任意顺序执行以下两种类型的操作零次或多次:
-
选择一对整数(i,j)(i,j)(i,j),使得1≤i<j≤2N1\leq i\lt j\leq 2N1≤i<j≤2N。交换SiS_iSi和SjS_jSj。这一操作的代价是AAA。
-
选择一个整数iii,使得1≤i≤2N1\leq i\leq 2N1≤i≤2N。将SiS_iSi替换为
(或)。这个操作的代价是BBB。
你的目标是使SSS成为一个正确的括号序列。求实现这一目标所需的最小总成本。可以证明,用有限次的运算总能达到目标。
什么是正确的括号序列。正确的括号序列是满足以下任意条件的字符串:
- 它是空字符串。
- 它是由
(,AAA,)按此顺序连接而成,其中AAA是一个正确的括号序列。 - 由AAA和BBB依次连接而成,其中AAA和BBB是正确的括号序列。
分析:
由题意得合法的括号不需要修改。
建立一个栈用于遍历括号序列,进栈,如果遇到栈顶是(,当前是),弹出栈顶,否则当前入栈
然后就会剩下
- (((((
- ))))))
- ))((((
这三种情况。
显然111和222只能修改一半使序列合法。333交换一次相当于修改两个位置。如果a<=2∗ba<=2*ba<=2∗b,一定要先执行交换操作。
模拟一下若有))((((,交换一次()(((),发现一次交换产生了两对合法括号。
交换完毕之后如果还有剩下的((((,那么只需要将一半翻转。
如果a>2∗ba>2*ba>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(数学)
题意:
问题陈述
给你NNN对整数(L1,R1),(L2,R2),…,(LN,RN)(L_1,R_1),(L_2,R_2),\dots,(L_N,R_N)(L1,R1),(L2,R2),…,(LN,RN)。这里,Li≤RiL_i\leq R_iLi≤Ri代表所有的1≤i≤N1\leq i\leq N1≤i≤N。
由NNN个整数组成的序列A=(A1,A2,…,AN)A=(A_1,A_2,\ldots,A_N)A=(A1,A2,…,AN)如果满足以下条件,则称为好整数序列:
- 所有1≤i≤N1\leq i\leq N1≤i≤N均为Li≤Ai≤RiL_i\leq A_i\leq R_iLi≤Ai≤Ri。
求使∑i=1N−1∣Ai+1−Ai∣\displaystyle\sum_{i=1}^{N-1}|A_{i+1}-A_{i}|i=1∑N−1∣Ai+1−Ai∣最小的的字典序最小的好整数序列AAA。
分析:
首先,对于1≤k≤N1\leq k\leq N1≤k≤N 和整数 xxx ,我们定义 fk(x)f_k(x)fk(x) 如下:
fk(x)=(The minimum value of∑i=kN−1∣Ai+1−Ai∣under the constraintsAk=x,Li≤Ai≤Ri (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))
从定义可知,fN(x)=0f_N(x)=0fN(x)=0。另外,我们对整数集合IkI_kIk定义如下:
Ik=argminx∈[Lk,Rk]∩Zfk(x)={x∈[Lk,Rk]∩Z∣fk(x)=minx′∈[Lk,Rk]∩Zfk(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]∩Zfk(x)=x′∈[Lk,Rk]∩Zminfk(x′)}.
那么,下面的命题成立。
命题 1
IkI_kIk可以表示为1≤k≤N1\leq k\leq N1≤k≤N的整数间隔。也就是说,存在整数lk,rk (lk≤rk)l_k,r_k\ (l_k\leq r_k)lk,rk (lk≤rk)这样的Ik=[lk,rk]∩ZI_k=[l_k,r_k]\cap\mathbb{Z}Ik=[lk,rk]∩Z。以下我们使用这些lk,rkl_k,r_klk,rk。
2. IN=[LN,RN]I_N=[L_N,R_N]IN=[LN,RN],而对于k<Nk\lt Nk<N、
- [Lk,Rk]∩Ik+1≠∅[L_k,R_k]\cap I_{k+1}\neq\emptyset[Lk,Rk]∩Ik+1=∅,则Ik=[Lk,Rk]∩Ik+1I_k=[L_k,R_k]\cap I_{k+1}Ik=[Lk,Rk]∩Ik+1。
- Ik={Rk}I_k= \lbrace R_k\rbraceIk={Rk},若Rk<lk+1R_k\lt l_{k+1}Rk<lk+1。
- Ik={Lk}I_k= \lbrace L_k\rbraceIk={Lk}若Lk>rk+1L_k\gt r_{k+1}Lk>rk+1。
让mk=minx∈[Lk,Rk]∩Zfk(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<N1\leq k\lt N1≤k<N、fk(x)={mk+1+lk−x(x<lk)mk+1(x∈[lk,rk])mk+1+x−rk(rk<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)
这个命题可以用归纳法按kkk的降序证明。根据命题1,存在整数lk,rkl_k,r_klk,rk,使得Ik=[lk,rk]I_k=[l_k,r_k]Ik=[lk,rk]。同时,根据命题1.2,我们可以确定每个lk,rkl_k,r_klk,rk按kkk的降序排列。
接下来,我们考虑构建词性最小解。根据I1I_1I1的定义,设置A1=minI1=l1A_1=\min I_1=l_1A1=minI1=l1即可。接下来,对于k≥2k\geq 2k≥2,当A1,…,Ak−1A_1,\dots,A_{k-1}A1,…,Ak−1确定后,我们考虑确定AkA_kAk。这里,在能使∣x−Ak−1∣+fk(x)|x-A_{k-1}|+f_k(x)∣x−Ak−1∣+fk(x)最小化的x∈[Lk,Rk]x\in[L_k,R_k]x∈[Lk,Rk]中,应选择最小的AkA_kAk。利用命题1.3进行计算,可以得出Ak=max{min{Ak−1,rk},Lk}A_k=\max\lbrace\min\lbrace A_{k-1},r_k\rbrace,L_k\rbraceAk=max{min{Ak−1,rk},Lk}。因此,我们可以按照kkk的升序确定每个AkA_kAk。
代码:
#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)
题意:
问题陈述
有一棵树,树上有NNN个顶点,编号为111至NNN。树的第iii条边双向连接顶点uiu_iui和viv_ivi。
对于(1,…,N)(1,\ldots,N)(1,…,N)的排列P=(P1,…,PN)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),假设(v1=1,v2,…,vk=i)(v_1=1,v_2,\ldots,v_k=i)(v1=1,v2,…,vk=i)是顶点111到顶点iii的简单路径,假设LiL_iLi是(Pv1,Pv2,…,Pvk)(P_{v_1},P_{v_2},\ldots,P_{v_k})(Pv1,Pv2,…,Pvk)的最长递增子序列的长度。我们定义f(P)=∑i=1NLif(P)=\sum\limits_{i=1}^N L_if(P)=i=1∑NLi。
给你一个整数KKK。请判断是否存在(1,…,N)(1,\ldots,N)(1,…,N)的排列PPP使得f(P)=Kf(P)=Kf(P)=K。如果存在,请给出一个这样的排列。
分析:
首先,判断什么情况无解。记sis_isi为以iii为根的子树大小,pip_ipi为iii的深度(d1=1d_1=1d1=1),显然有k<nk \lt nk<n或k≤∑pik \le \sum p_ik≤∑pi时无解(因为有1≤Li≤di1≤L_i≤d_i1≤Li≤di)。以下将通过构造说明其他情况(k∈[n,∑pi]k∈[n,\sum p_i]k∈[n,∑pi])都有解。
更改点iii的pip_ipi会造成很多点的LjL_jLj发生变化,所以这不太好考虑。
我们直接假设最终构造中每个点路径上的LISLISLIS一定是它父节点的LISLISLIS拼上(或不拼上)自己。这样如果iii在111到iii的路径的LISLISLIS上,iii一定也在子树中所有点的LISLISLIS上,这个点就会对答案f(p)f(p)f(p)造成sis_isi的贡献;否则就造成000的贡献。
发现这变成了一个背包问题:我们需要选择若干个节点(111是必选)使得它们的贡献sis_isi加起来恰好为kkk。
注意到如果我们把sis_isi按从大到小排序,就有si≤∑sj+1(j>i)s_i≤\sum s_j +1 (j>i)si≤∑sj+1(j>i)(因为一个点的子树大小就是它所有的儿子的子树大小和+1+1+1,那这个点最大也不过是比它小的所有点都是它的儿子),因此我们从大到小贪心,每次遇到k≥sik≥s_ik≥si就可以从kkk中减掉sis_isi,剩下的部分一定可以在后面拼出。
好了,我们已经决定哪些点要造成贡献了,答案该怎么构造呢?
为了保证所有没取到的点一定不造成贡献,我们把所有没取到的点的权值设为比取到的点的权值小,这样就不会拼在已经形成的LISLISLIS的后面;为了保证没取到的点不会自己形成一段LISLISLIS,我们只要让所有没取到的点的权值都从根往下降序排列就行。同理,既然取到的点要形成LISLISLIS,那么这些点的权值就应该从根往下升序排列。
代码:
#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,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

739

被折叠的 条评论
为什么被折叠?



