AtCoder Regular Contest 175 A~D

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 1iN)的左边和右边,分别有勺子 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,由LR?组成。在 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 1i<j2N。交换 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 1i2N。将 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. (((((
  2. ))))))
  3. ))((((

这三种情况。

显然 1 1 1 2 2 2只能修改一半使序列合法。 3 3 3交换一次相当于修改两个位置。如果 a < = 2 ∗ b a<=2*b a<=2b,一定要先执行交换操作。

模拟一下若有))((((,交换一次()(((),发现一次交换产生了两对合法括号。

交换完毕之后如果还有剩下的((((,那么只需要将一半翻转。

如果 a > 2 ∗ b a>2*b a>2b,那么我们只能翻转。)))(((,左半边翻转一半,右半边翻转一半,如果是奇数还剩下两个括号)(,那么还需要再翻两遍。

代码:

#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 LiRi代表所有的 1 ≤ i ≤ N 1\leq i\leq N 1iN

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 1iN均为 L i ≤ A i ≤ R i L_i\leq A_i\leq R_i LiAiRi

求使 ∑ i = 1 N − 1 ∣ A i + 1 − A i ∣ \displaystyle\sum_{i=1}^{N-1}|A_{i+1}-A_{i}| i=1N1Ai+1Ai最小的的字典序最小的好整数序列 A A A

分析:

首先,对于 1 ≤ k ≤ N 1\leq k\leq N 1kN 和整数 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=kN1Ai+1Aiunder the constraintsAk=x,LiAiRi (k+1iN))

从定义可知, 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 1kN的整数间隔。也就是说,存在整数 l k , r k   ( l k ≤ r k ) l_k,r_k\ (l_k\leq r_k) lk,rk (lkrk)这样的 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 1k<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+lkxmk+1mk+1+xrk(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 k2,当 A 1 , … , A k − 1 A_1,\dots,A_{k-1} A1,,Ak1确定后,我们考虑确定 A k A_k Ak。这里,在能使 ∣ x − A k − 1 ∣ + f k ( x ) |x-A_{k-1}|+f_k(x) xAk1+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{Ak1,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 (1iN),假设 ( 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=1NLi

给你一个整数 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 kpi时无解(因为有 1 ≤ L i ≤ d i 1≤L_i≤d_i 1Lidi)。以下将通过构造说明其他情况( 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) sisj+1(j>i)(因为一个点的子树大小就是它所有的儿子的子树大小和 + 1 +1 +1,那这个点最大也不过是比它小的所有点都是它的儿子),因此我们从大到小贪心,每次遇到 k ≥ s i k≥s_i ksi就可以从 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,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值