原题链接:Dashboard - Codeforces Round #849 (Div. 4) - Codeforces
目录
G1. Teleporters (Easy Version)
G2. Teleporters (Hard Version)
A. Codeforces Checking
给定一个小写字母,判断字符串"Codeforces"里是否有这个字符。
void solve() {
string s="codeforces";
char c;
cin>>c;
for(char x:s) {
if(c==x) {
yes;
return;
}
}
no;
}
B. Following Directions
在平面直角坐标系给定初始坐标 (0,0) 以及移动方向,判断是否经过或到达 (1,1) 点。
void solve() {
int n,x=0,y=0;
string s;
cin>>n>>s;
for(char c:s) {
if(c=='U')y++;
else if(c=='D')y--;
else if(c=='R')x++;
else if(c=='L')x--;
if(x==1&&y==1) {
yes;
return;
}
}
no;
}
C. Prepend and Append
字符串 t 可以在一边加上一个 1 ,另一边加上一个 0 ,最后给定的结果为 s 。求 t 的最短长度。
void solve() {
int n;
string s;
cin>>n>>s;
int l=0,r=n-1;
while(l<r) {
if(s[l]-'0'+s[r]-'0'!=1)break;
l++,r--;
}
cout<<r-l+1<<endl;
}
D. Distinct Split
定义f(x)为字符串 x 中不同字符的数量。
给定一个字符串 s ,将它分成两个非空字符串 a 和 b ,使得 f(a)+f(b) 是可能的最大值,求该最大值。
思路:我们可以枚举前1~i个字符为一组,后面的i+1~n个为一组,模拟一下即可。若一个字符出现次数从1变为0了,f(x)则要减一,若一个字符出现次数从0变为1了,f(x)则要加一,其它时候f(x)都不会改变。
void solve() {
mem(a,0),mem(b,0);
int n,sum1=1,sum2=0,ans;
string s;
cin>>n>>s;
a[s[0]]++;
FOR(1,n-1){
if(b[s[i]]==0)sum2++;
b[s[i]]++;
}
ans=sum1+sum2;
FOR(1,n-2){
if(a[s[i]]==0)sum1++;
if(b[s[i]]-1==0)sum2--;
a[s[i]]++,b[s[i]]--;
ans=max(ans,sum1+sum2);
}
cout<<ans<<endl;
}
E. Negatives and Positives
给定一个包含 n 个元素的数组 a ,在执行以下操作任意次后,找到数组可能的最大和:
选择 2 个相邻元素并翻转它们的符号。
换句话说,选择一个索引 i ,使 1≤i≤n−1 ,并分配 a[i]=−a[i] 和 a[i+1]=−a[i+1] 。
思路:若负数的个数为偶数个,那么我们一定能使这些负数全都变为正数。因为对于单个负数,题意中操作的本质,就是让负号往前移一位,或者让负号往后移一位,比如 1 -2 3 这组数据,若要将负号往前移一位,则可以操作第一个数和第二个数,数组就变成了-1 2 3,同理想让负号往后移一位,数组就变成了1 2 -3。并且当两个负号挨着的时候,我们可以让它俩同时变成正号。所以当负数个数为偶数时,我们可以让所有负数挤在一起,然后使他们两两变为正数。若负数个数为奇数个的话,则数组中必然有且只有一个负数,我们让那个绝对值最小的数变为负数即可。
void solve() {
int n,sum=0,ans=0;
cin>>n;
FOR(1,n) {
cin>>a[i];
if(a[i]<0)sum++,a[i]=-a[i];
ans+=a[i];
}
sort(a+1,a+n+1);
if(sum%2)ans-=2*a[1];
cout<<ans<<endl;
}
F. Range Update Point Query
给定一个数组 a ,你总共需要处理 q 次更新和两种类型的查询:
1-l-r 对于 l≤i≤r 将 a[i]的值更新为a[i]的位数之和。
2-x 输出 a[x] 。
思路:我们可以发现,对于一个数x,我们最多进行三次1操作,它之后就不会再改变了。所以我们可以用一个set来存储操作后仍会改变的数字下标,对于每次1操作我们先用lower_bound函数查找下标大于等于l的、操作后数字仍会改变的下标t,若t>r了,则说明l~r区间内的数不会再改变了。而若t<=r,我们则对这个下标t所对应的数字进行操作,并且若a[t]=f(a[t])了,我们就将下标t从set中删除,代表下标t所对应的数字不会再改变了。
int f(int x) {
string s=to_string(x);
int sum=0;
for(char c:s)sum+=c-'0';
return sum;
}
void solve() {
int n,m,sum=0;
set<int>s;
cin>>n>>m;
s.insert(n+1);
for(int i=1; i<=n; i++) {
cin>>a[i];
if(f(a[i])!=a[i])s.insert(i);
}
for(int i=1; i<=m; i++) {
int op;
cin>>op;
if(op==2) {
int x;
cin>>x;
cout<<a[x]<<endl;
} else {
int l,r;
cin>>l>>r;
while(1) {
auto t=*s.lower_bound(l);
if(t>r)break;
a[t]=f(a[t]);
if(a[t]==f(a[t]))s.erase(t);
l=t+1;
}
}
}
}
G1. Teleporters (Easy Version)
考虑数轴上的点 ,,(0,1,…,n) 在 ,,1,2,…,n 的每个点上都有一个传送器。在第 i 点,你可以这样做:
- 向左移动一个单位:花费 1 枚硬币。
- 向右移动一个单位:花费 1 枚硬币。
- 在i点使用传送器(如果存在的话):它需要 a[i] 金币。因此,你可以传送到点 0 。一旦你使用了传送器,你就不能再使用了。
你有 c 个硬币,从 0 点开始,最多能使用多少个传送器?
思路:由题意可知使用第i个传送器的花费为a[i]+i个硬币,所以我们将a[i]+i存入一个数组,由小到大排序,然后依次遍历看看m个硬币能使用前几个传送器。
void solve() {
int n,m,sum=0;
cin>>n>>m;
FOR(1,n)cin>>a[i],a[i]+=i;
sort(a+1,a+n+1);
FOR(1,n)if(m-a[i]>=0)sum++,m-=a[i];
cout<<sum<<endl;
}
G2. Teleporters (Hard Version)
考虑数轴上的点 ,,(0,1,…,n) 在 ,,1,2,…,n 的每个点上都有一个传送器。在第 i 点,你可以这样做:
- 向左移动一个单位:花费 1 枚硬币。
- 向右移动一个单位:花费 1 枚硬币。
- 在i点使用传送器(如果存在的话):它需要 a[i] 金币。因此,你可以传送到点 0 ,或者传送到点n+1。一旦你使用了传送器,你就不能再使用了。
你有 c 个硬币,从 0 点开始,最多能使用多少个传送器?
思路:对于每个传送器,我们花费的代价有两个:a[i]+i或者a[i]+n-i+1。若不考虑起始点为0的话,只需要将这两个花费取min然后排序即可。但是因为起始点为0,所以我们所选的第一个传送器它的花费必然是a[i]+i,因此我们枚举所有传送器作为第一个传送器的情况,具体实现见代码注释。
void solve() {
int n,m,ans=0;
cin>>n>>m;
FOR(1,n) cin>>a[i],b[i]=min(a[i]+i,a[i]+n-i+1);
sort(b+1,b+n+1);
FOR(1,n)s[i]=s[i-1]+b[i];//使用前i个传送器的最小代价
FOR(1,n){
if(a[i]+i>m)continue;//若使用第一个传送器的代价就比m大则说明这次a[i]对应的答案为0
int p=min(a[i]+i,a[i]+n-i+1);//p表示a[i]在b数组中对应的数
int k=upper_bound(s+1,s+n+1,m-(a[i]+i))-(s+1);//m-a[i]-i为使用了第一个传送器之后的剩的钱,二分查找剩下的钱能使用多少个传送器。
//若b[k]>=p则说明我们会将第一次使用传送器的代价算进花费,然而实际上我们已经将第一个使用的传送器的代价减掉了
if(b[k]>=p)k=upper_bound(s+1,s+n+1,m+p-(a[i]+i))-(s+1),ans=max(ans,k);//因为第一个传送器花费的实际代价可能不是a[i]-i,所以我们的钱要减少a[i]+i-p,也就是"补差价"
else ans=max(ans,k+1);//若b[k]<p则说明前k个里不包含我们的第一个传送器,所以答案是k+1
}
cout<<ans<<endl;
}