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,∣a−b∣,∣a−b∣≤1∣a−b∣>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;
}