【解题报告】CF 704 (Div. 2) | ABCD

【补充】

A:Three swimmers | 数学

  • 【题意】
    三个人同时开始无限跑圈,三个人分别一圈跑 a 、 b 、 c a、b、c abc 分钟。
    问开始后第 p p p 分钟,在起点处最少等多少时间能遇到下一个人。
  • 【范围】
    样例组数 T ≤ 1000 T\le 1000 T1000
    1 ≤ p , a , b , c ≤ 1 0 18 1\le p,a,b,c\le 10^{18} 1p,a,b,c1018
  • 【思路】赛内
    签到题,推出最简的式子之后判断即可。
    每个人的跑一圈时间相当于循环节,用时间取模循环节,就是目前的进度。
    用循环节减去目前的进度就是剩余时间。
    注意目前进度如果为 0 0 0 表示已经在起点,再取模即可。
  • 【代码】
    时间复杂度: O ( T ) O(T) O(T)
/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
const int MAX = 2e5+50;

int main()
{
    int T;scanf("%d",&T);
    while(T--){
        ll p,a,b,c;scanf("%lld%lld%lld%lld",&p,&a,&b,&c);
        a = (a - p % a) % a;
        b = (b - p % b) % b;
        c = (c - p % c) % c;
        printf("%lld\n",min(min(a,b),c));
    }
    return 0;
}

B:Card Deck | 贪心

  • 【题意】
    n n n 张牌, p i p_i pi 表示从牌底到牌顶第 i i i 张牌的编号。编号是个 n n n 的全排列。
    你可以第一堆卡,选择牌顶的 k k k 张牌,按照这个顺序把在这几张牌放在终点堆。
    最后第一堆卡被拿空了,得分为终点堆按这个式子计算: ∑ i n n − i × p i \sum_i n^{n-i}\times p_i inni×pi
  • 【范围】
    1 ≤ n ≤ 1 0 5 1\le n\le 10^5 1n105
  • 【思路】赛内
    首先随便看看,式子按照 i i i 增加,左边的大系数会递减。我们就希望越靠近前面的 i i i p i p_i pi 越大。
    然后看一下样例,就随便得到了结论:
    假设拿牌的范围为 [ s t , e d ] [st,ed] [st,ed],我们的要求是 ∀ i ∈ [ s t , e d ] \forall i\in[st,ed] i[st,ed] 满足 p s t ≥ p i p_{st}\ge p_i pstpi 并且 p s t < p e d + 1 p_{st}<p_{ed+1} pst<ped+1
    具体原因的话,因为此时拿 p e d + 1 p_{ed+1} ped+1 放到前面更优啥的。
    用队列模拟一下就行了。
  • 【代码】
    时间复杂度: O ( n ) O(n) O(n)
/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
const int MAX = 2e5+50;
const int INF = 0x3f3f3f3f;

int aa[MAX];
deque<int>Q;
int main()
{
    int T;scanf("%d",&T);
    while(T--){
        int n;scanf("%d",&n);
        for(int i = 1;i <= n;++i)scanf("%d",&aa[i]);
        for(int i = 1;i <= n;++i){
            int st = i;int ed = i;
            while(ed + 1 <= n && aa[ed+1] < aa[st])ed++;
            for(int j = ed;j >= st;--j)Q.push_front(aa[j]);
            i = ed;
        }
        while(!Q.empty()){
            printf("%d ",Q.front());
            Q.pop_front();
        }
        puts("");
    }
    return 0;
}

C:Maximum width | 贪心 + 思维

  • 【题意】
    给你两个字符串 s 、 t s、t st,然后 n = ∣ s ∣ , m = ∣ t ∣ n=|s|,m=|t| n=sm=t
    你需要得到一个序列 p [ 1 , 2 , ⋯   , m ] p[1,2,\cdots,m] p[1,2,,m],满足 1 ≤ p 1 < p 2 < ⋯ < p m ≤ n 1\le p_1<p_2<\cdots<p_m\le n 1p1<p2<<pmn
    然后 ∀ i ∈ [ 1 , m ] \forall i\in[1,m] i[1,m],满足 s p i = t i s_{p_i}=t_i spi=ti
    定义该序列的宽度为 max ⁡ i { p i + 1 − p i } \max_i\{p_{i+1}-p_i\} maxi{pi+1pi}
    保证该序列至少存在一个。
    你需要给出宽度的最大值
  • 【范围】
    2 ≤ m ≤ n ≤ 2 × 1 0 5 2\le m\le n\le 2\times 10^5 2mn2×105
  • 【思路】赛内
    挺有意思的题目。首先那个序列相当于是 L C S LCS LCS 最长公共子序列的感觉。
    但是要求宽度最大,一时不知道哪个字母该对应哪个字母。
    然后我们想到了,对于某一个位置 i i i ,该位置之前的字母都优先左匹配,该位置之后的字母都优先右匹配
    在这里插入图片描述
    这样时间复杂度不够。
    那我们就想到了:位置 i i i 一步一步挪过来,貌似只影响一个字母的匹配位置。
    假设一开始所有的字母都是优先左匹配,然后倒着去处理,把最右边的优先左匹配的点改成优先右匹配。然后去求间隔的最大值。
    优先右匹配,就是去记录每一个字母出现的最右合法位置,可以用一个队列 q u e u e queue queue 去处理。
    那么什么位置是不合法的呢?比如目前该位置的字母的最右合法位置为 y o u you you ,那么接下来的所有字母的右匹配位置都不能在 y o u you you 位置的右边。
  • 【代码】
    时间复杂度: O ( ∣ n ∣ + ∣ m ∣ ) O(|n| + |m|) O(n+m)
/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
const int MAX = 2e5+50;
const int INF = 0x3f3f3f3f;

int n,m;
string s,t;
int pos[MAX];
int ans;
void match(){		/// 优先左匹配,记录 p[]
    int st = 1;
    pos[0] = INF;
    for(int i = 1;i <= m;++i){
        while(s[st] != t[i])st++;
        pos[i] = st;
        st++;
        ans = max(ans,pos[i] - pos[i-1]);
    }

}
queue<int>last[30];
void solve(){
    int R = m;
    int hou = INF;
    for(int i = n;i >= 1;--i){
        if(i > pos[R]){				/// 这些字母的位置是可以用的
            last[s[i] - 'a'].push(i);
        }else{						/// 优先右匹配
            while(last[s[i] - 'a'].size() && last[s[i] - 'a'].front() > hou)last[s[i] - 'a'].pop();
            if(last[s[i] - 'a'].empty()){		/// 右匹配的位置就是目前位置
                hou = i;
            }else{
                pos[R] = last[s[i] - 'a'].front();		/// 右匹配该字母最右合法位置
                hou = last[s[i] - 'a'].front();			/// 最后位置更新
                last[s[i] - 'a'].pop();					/// 该位置没了
                last[s[i] - 'a'].push(i);				/// 多了一个新位置
                ans = max(ans,pos[R] - pos[R-1]);
            }

            R--;
            if(R == 0)break;
        }
    }
}
int main()
{
    cin >> n >> m >> s >> t;
    s = " " + s;
    t = " " + t;
    match();
    show(ans);
    solve();
    cout << ans;
    return 0;
}

D:Genius’s Gambit | 位运算 + 构造

  • 【题意】
    给你数字 a , b , k a,b,k a,b,k
    你需要构造两个二进制串 x , y x,y x,y,满足他们没有前导零,且 x ≥ y x\ge y xy
    首先,这两个串的 0 0 0 的个数都等于 a a a
    其次,这两个串的 1 1 1 的个数都等于 b b b
    其次, ( x − y ) 2 (x-y)_2 (xy)2 1 1 1 的个数等于 k k k,就是两个数字相减后的二进制表示的 1 1 1 的个数为 k k k
    问你怎么构造,或者无法构造。
  • 【范围】
    0 ≤ a 0\le a 0a
    1 ≤ b 1\le b 1b
    0 ≤ k ≤ a + b ≤ 2 × 1 0 5 0\le k\le a+b\le 2\times 10^5 0ka+b2×105
  • 【思路】赛内 + 赛后 ( % % % s o l e m n t e e % % % ) \color{red}(\%\%\%solemntee\%\%\%) (%%%solemntee%%%)
    默认以下竖式都是减法
    首先有这么几种简单的消耗操作
    1111 1111 − − − − − − 0000 \begin{matrix} 1111\\ 1111\\ ------\\ 0000 \end{matrix} 111111110000
    可以把两个串的多余的 1 1 1 消耗掉。
    0000 0000 − − − − − − 0000 \begin{matrix} 0000\\ 0000\\ ------\\ 0000 \end{matrix} 000000000000
    可以把两个串的多余的 0 0 0 消耗掉。
    也就是说, a , b a,b ab 明显是少了不顶用,多了没关系的。接下来看怎么产生 1 1 1
    1000 0001 − − − − − − 0111 \begin{matrix} 1000\\ 0001\\ ------\\ 0111 \end{matrix} 100000010111
    这是一个产生 1 1 1 的关键式子。消耗了 a a a 0 0 0 和一个 1 1 1 ,就可以产生 a a a 1 1 1
    然后我们按照这个思路去做, k k k 的上限为 a a a ,在做一些特判,结果 W a Wa Wa 了。
    为啥呢?我们还有更神奇的式子
    1011100 0011101 − − − − − − 0111111 \begin{matrix} 1011100\\ 0011101\\ ------\\ 0111111 \end{matrix} 101110000111010111111
    消耗了 a a a 0 0 0 b b b 1 1 1 ,就可以产生 a + b − 1 a+b-1 a+b1 1 1 1。这是具有决定意义的式子。
    然后由于 y y y 串不能有前导零,必须每个式子花掉一个 1 1 1
    于是 k k k 的最大上限为: a + b − 2 a+b-2 a+b2,前提是 b ≥ 2 b\ge 2 b2
    【总结一下】
    k = 0 k=0 k=0 的时候有些特殊,只要前面全放 1 1 1 后面全放 0 0 0 就行了。
    k > 0 k>0 k>0 的时候, 1 1 1 必须要用两个(一个开头一个产生 1 1 1), 0 0 0 必须要用一个(为了产生 1 1 1),上限为 a + b − 2 a+b-2 a+b2
    首先我们需要拿一个 1 1 1 作为开头。接下来产生剩余的 1 1 1
    然后我们构造
    1 x x x x x 0 0 x x x x x 1 − − − − − − 0111111 \begin{matrix} 1xxxxx0\\ 0xxxxx1\\ ------\\ 0111111 \end{matrix} 1xxxxx00xxxxx10111111
    ,其中 x x x 的要求:上下都为 1 1 1 或上下都为 0 0 0 都可。
    最后一些多余的 1 、 0 1、0 10,我们直接消耗掉即可。
    即最终的解长得样子为:
    11 x x x x x 01 ⋯ 10 ⋯ 0 10 x x x x x 11 ⋯ 10 ⋯ 0 − − − − − − − − − − − 0011111110 ⋯ 00 ⋯ 0 \begin{matrix} 11xxxxx01\cdots10\cdots0\\ 10xxxxx11\cdots10\cdots0\\ -----------\\ 0011111110\cdots00\cdots0 \end{matrix} 11xxxxx0110010xxxxx111000011111110000
  • 【代码】
    时间复杂度: O ( a + b + k ) O(a+b+k) O(a+b+k)
/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
const int MAX = 2e5+50;

queue<int>A,B;
int main()
{
    int a,b,k;cin >> a >> b >> k;
    if(k == 0){
        for(int i = 1;i <= b;++i)
            A.push(1),B.push(1);
        for(int i = 1;i <= a;++i)
            A.push(0),B.push(0);
        puts("Yes");
        while(!A.empty())cout << A.front(),A.pop();
        puts("");
        while(!B.empty())cout << B.front(),B.pop();
        return 0;
    }
    if(a + b - 2 < k || b == 1 || a == 0)puts("No");
    else{
        b--;
        A.push(1);B.push(1);
        a--;b--;
        A.push(1);B.push(0);
        k--;
        for(int i = 1;i <= k;++i){
            if(b){
                b--;
                A.push(1);B.push(1);
            }else{
                a--;
                A.push(0);
                B.push(0);
            }
        }
        A.push(0);B.push(1);

        puts("Yes");
        for(int i = 1;i <= b;++i)
            A.push(1),B.push(1);
        for(int i = 1;i <= a;++i)
            A.push(0),B.push(0);
        while(!A.empty())cout << A.front(),A.pop();
        puts("");
        while(!B.empty())cout << B.front(),B.pop();

    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值