cf #800 Div.2

cf #796 Div.2

A. Creep

  • 题意

​ 定义一个只含01的前缀字符串中01的个数差为分数,定义creep为字符串s的所有前缀字符串分数的最大值,现给你a个0,b个1,要你构造出creep最小的01串

  • 题解

    我们知道给定a,b后,字符串的分数一定含有|a-b|,1;前者在对于整个字符串求分数时一定有|a-b|,后者单字符串为一个字符时分数为1。即
    c r e e p = m a x { 1 , ∣ a − b ∣ ≤ 1 ∣ a − b ∣ , ∣ a − b ∣ > 1 creep=max \begin{cases} 1,&|a-b|\leq1\\ |a-b|,&|a-b|>1 \end{cases} creep=max{1,ab,ab1ab>1

​ 有这样一种构造方式符合上述式子,每次都构造一个“01”直到a或者b用完,若某种数字还有剩余,则在之前的01串后一个加那个数字。因为前面构造的01一定不超过1或者|a-b|;后面一旦要一直添加某个数时,已经说明creep=|a-b|了,即只需整个串不超过|a-b|,而随着0101…111这样构造,那么前缀分数一定是1,0,1,0…1,2,3,|a-b|不超过|a-b|。

注意:前缀字符串,不是子串或者子序列,前缀是以某个字符下标x为结尾的str[0,x];子串是连续截取的某段字符;子序列是只要从头到尾的顺序取出的某些字符就是

  • 代码
#include <iostream>

using namespace std;

void solve() {
    int a,b;
    cin>>a>>b;
    while(a && b){
        cout<<"01";
        a--;b--;
    }
    while(a){ cout<<"0";a--; }
    while(b){ cout<<"1";b--; }
    cout<<'\n';
}

int main() {
    int T;
    cin>>T;
    while(T--) solve();
    
    return 0;
}

B. Paranoid String

  • 题意

    给定一个01字符串,有以下操作:“01”->“1” , “10”->“0”;问有多少个子串可以通过此操作把子串变为一个字符

  • 题解

    首先,当子串长度为1时,一定符合条件,所以ans+=n;

    同时,只有当子串有一个01的转变时,才有可以操作的余地,因为连续一样的数字的子串无法操作。以连续的是0为例

    前提:0下标开始,字符串以下标i元素结尾的(不是一个元素的)子串数为i,str子串总数为n(n+1)/2*

    0 0 0 1 0 0 0

    以1结尾的子串一定可以符合要求变成一个数1。ans+=3;

    1与划线0又有一个转变,证明其符合要求:1结尾的子串看成一个数字1,那么字符串变成1000,符合要求。ans+=4

    0 0 0 1 1 1 1

    以划线1结尾的子串一定可以符合要求变成一个数1。ans+=3;

    划线1与后面没有转变,证明其不符合要求:1结尾的子串看成一个数字1,那么字符串变成1111,不符合要求。

    结论:遍历每个元素,当出现转变时,计算所有以该转变元素为结尾的非长度为1的子串数量,即为长度不为1的符合要求的子串。

  • 代码

#include <iostream>
#include <cstring>

using namespace std;

long long solve() {
    int n;
    string s;
    cin>>n>>s;
    
    long long ans=n;//长度为1的子串
    for(int i=1;i<n;i++)
        if(s[i-1]!=s[i]) ans+=i;//找所有转变点,把以转变点结尾的子串数加入答案
        
    return ans;
}

int main() {
    int T;
    cin>>T;
    while(T--) cout<<solve()<<'\n';
    
    return 0;
}

C. Directional Increase

  • 题意

    给定一个初始值全为0的长度为n的数组,通过以下操作:向右走一步该点值+1,向左走一步该点值-1。能否变成给定的数组,同时走到了最左端

  • 题解

    从最左端出发向右走,再向左走回到最左端,意味着加的数和减的数相同,即从最左端到走到的最右端之间的数之和为0,走到的最右端的右边如果还存在数,那么应当全为0(因为没走到过),可以推得全部和为0

    最右端的数一定为负数,由于前缀和为0,所以除去最右端的前缀和一定大于0

    那么,题目变为前缀和为0的点为走到的最右端,该点之后不能存在非0数。同时,前缀和一定大于0。以及,所有数之和应当为0

  • 代码

#include <iostream>
#include <cstring>

using namespace std;
const int N=2e5+5;

int n;
int a[N];

bool solve() {
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    
    bool flag=0;//判断是否已经存在前缀和为0
    long long sum=0;
    for(int i=1;i<=n;i++) {//判断中间是否存在不合法情况
        if(flag && a[i]!=0) return false;//在前缀为0的右边不能有数为0
        sum+=a[i];//前缀和
        if(sum<=0) flag=1;//如果出现前缀和为0,意味着此点已经为走到的最右端
    }						//如果出现前缀和小于0,一定不合法
        
    return sum==0;//总和是否为0
}

int main() {
    int T;
    cin>>T;
    while(T--) cout<<(solve() ? "Yes":"No")<<'\n';
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值