这场简单,只有F题比较值得一做,C是贪心,D是个BFS,E是构造,F是不太明显的线段树。
A 小红的数位删除
思路:
用 string
的提取字串函数 substr()
即可。
code:
cpp
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
string s;
int main(){
cin>>s;
cout<<s.substr(0,s.length()-3);
return 0;
}
B 小红的小红矩阵构造
思路:
数据很小 n , m ≤ 100 n,m\le 100 n,m≤100 ,直接暴力,统计一下总和,算出每一行每一列的异或值,看一下是否满足条件即可。
code:
cpp
#include <iostream>
#include <cstdio>
#include <set>
using namespace std;
const int maxn=105;
int n,m,x,a[maxn][maxn];
long long tot;
int main(){
cin>>n>>m>>x;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
a[i][0]^=a[i][j];
a[0][j]^=a[i][j];
tot+=a[i][j];
}
}
if(tot!=x){
puts("wrong answer");
return 0;
}
set<int> S;
for(int i=1;i<=n;i++)S.insert(a[i][0]);
for(int j=1;j<=m;j++)S.insert(a[0][j]);
puts((S.size()==1)?"accepted":"wrong answer");
return 0;
}
C 小红的白色字符串
思路1:
这题做到身败名裂。
其实就是很简单的贪心策略,如果某个字符是个不满足条件的字符就直接涂白掉就行了,注意涂白不是删除。
为什么这么贪心是对的:我们每次看到的某个位置的字符不满足条件,说明这个字符是个大写字母,并且前面那个字符没有被涂白,这时候我们可以涂白前面的字符,也可以涂白这个字符,显然涂白这个字符能给后面的字符留出更多操作空间,因此这样贪心就是对的。
code1:
cpp
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int cnt;
string s;
bool isu(char ch){
return ch>='A' && ch<='Z';
}
int main(){
cin>>s;
for(int i=1;i<s.length();i++){
if(isu(s[i]) && s[i-1]!='!'){
s[i]='!';
cnt++;
}
}
cout<<cnt;
return 0;
}
思路2:
模拟一下这个过程,其实就是遇到一段长为 l e n len len 的连续的大写字母时,把这一段上第 1 , 3 , 5 , ... 1,3,5,\dots 1,3,5,... 位置上的字符涂白,这就需要涂白 ⌈ l e n 2 ⌉ \left\lceil\dfrac{len}2\right\rceil ⌈2len⌉ 个位置。所以找一下有多少个大写字母段,对每段数一下贡献也是可以的。
code2:
cpp
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
string s;
int cnt;
inline bool isu(char x){
return x>='A' && x<='Z';
}
int main(){
cin>>s;
for(int l=1,r=0;l<s.length();l++){
if(isu(s[l])){
r=l+1;
while(r<s.length() && isu(s[r]))r++;
cnt+=(r-l+1)/2;
l=r;
}
}
cout<<cnt;
return 0;
}
D 小红走矩阵
思路:
就是个很板的BFS,无非就是下一次移动多一个 不能走到和当前位置字母相同的位置上 的限制条件而已。
code:
cpp
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#define mk make_pair
using namespace std;
const int maxn=1005;
const int inf=1e9;
int n,m;
string mp[maxn];
bool vis[maxn][maxn];
int d[maxn][maxn];
int fx[]={1,-1,0,0},fy[]={0,0,1,-1};
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>mp[i];
mp[i]=" "+mp[i];
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
d[i][j]=inf;
queue<pair<int,int> > q;
q.push(mk(1,1));
d[1][1]=0;
while(!q.empty()){
int ux=q.front().first,uy=q.front().second;
q.pop();
if(ux==n && uy==m){
cout<<d[n][m];
return 0;
}
if(vis[ux][uy])continue;
else vis[ux][uy]=true;
for(int i=0,x,y;i<4;i++){
x=ux+fx[i];
y=uy+fy[i];
if(x<1 || x>n || y<1 || y>m || mp[x][y]==mp[ux][uy])continue;
if(d[x][y]>d[ux][uy]+1){
q.push(mk(x,y));
d[x][y]=d[ux][uy]+1;
}
}
}
cout<<-1;
return 0;
}
E 小红的小红走矩阵
思路:
构造题,这几个限制条件说的其实就是:
- 答案路径不能遍历整个图。
- 答案路径必须拐个弯,不能只靠向左向下就行。
- 某个字母不能出现超过一半的位置。
- 必须存在答案路径。
那我们的构造可以分成两种思路,一种是先让答案路径必须遍历整个图,然后再开辟一条近路,一种是先让答案路径可以简单的走到最下面,然后再走到最右边,然后再在必经之路上卡住,让答案路径绕一下远路。直觉上第二种更简单,因此尝试第二种。
让路径一直向下走到头而不从中间跑出去,我们可以让相邻两行不同,然后第二列和第一列保持一致。走到最下边后,想要不能只向右走,可以在某个相邻两个位置放上一对相同的字符,这样答案就必须向上绕路。这样构造出来了,其他位置无所谓,为了不出现某个字符很多的情况,可以在没必要的位置用 26 26 26 个字母轮着铺。
code:
cpp
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=1e3+5;
int n,m;
char mp[maxn][maxn];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
mp[i][j]=(i+j)%26+'a';
for(int i=1;i<n;i++)mp[i][2]=mp[i][1];
mp[n][m-1]=mp[n][m];
for(int i=1;i<=n;i++)cout<<mp[i]+1<<endl;
return 0;
}
F 小红的好子串询问
思路:
很棒的线段树题,这个问法就很线段树。其实一开始读假题了,想成是不允许有长度大于2的回文串,不然赛时应该能A掉的。
考虑如何满足不允许有长度不小于2的回文串 的限制条件。显然所有回文串都是由更小的回文串两边加相同字符得到的,因此我们只要保证序列中没有长为 2 2 2 和 3 3 3 的回文串,就可以保证不存在更长的回文串。
没有长为 2 2 2 的回文串,意味着相邻字符不同,没有长为 3 3 3 的回文串,意味着隔一个字符的两个字符也不同。这其实就是规定了连续的三个字符一定各不同 ,加上只能用这三种字符的限制条件,那么这个字符串的字符一定是以 3 3 3 为周期在循环的。也就说,只要确定了最前面三个字符的排列方法,我们就知道了整个字符串是什么。
而最前面三个字符的排列只有 A 3 2 = 6 A_3^2=6 A32=6 种情况。即 red
,edr
,dre
,rde
,der
,erd
。如果用 012 012 012 分别代表 r e d red red,那么就是 012
,120
,201
,021
,210
,102
。把一个字符串修改为满足条件的串,就相当于把这个串修改成上述的任意一种的串。那么这个题的操作 2 2 2 就变成了统计把一段子串修改为上述六种其一的串的最少修改次数。
因为要在 l o g log log 时间内对某个区间的信息进行统计,考虑线段树维护。但是两个区间也就是串的合并规则并不明确。
考虑如何将两个串合并。发现 012
,120
,201
相当于一个串的循环,同理 021
,210
,102
。如果一个 012
类型的串后面接上另外一个串仍然保持 012
类型,那么就需要看前面那个串的长度。如果前面的串模 3 3 3 有余数的话,这个多出来的一小部分相当于给后面的串补了一点点,比如余 1 1 1,前面的串相当于给后面的串补了一个最前面的 0 0 0,后续只需要循环补上 120 120 120 就行了。于是有:
- 如果长度能被 3 3 3 整除,那么后面就接 012 012 012 类型。
- 如果长度能除 3 3 3 余 1 1 1,那么后面就接 120 120 120 类型。
- 如果长度能除 3 3 3 余 2 2 2,那么后面就接 201 201 201 类型。
同理,对其他情况进行一下讨论,就可以得到合并规则。这样就可以使用线段树进行维护了。
code:
cpp
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=1e5+5;
int n,Q;
struct segment_tree{
#define ls p<<1
#define rs p<<1|1
struct Node{
//r-0 e-1 d-2
//修改为012 120 201 021 102 210序列的修改次数
int x[6],len;
Node(int a=0,int b=0,int c=0,int d=0,int e=0,int f=0){
x[0]=a;
x[1]=b;
x[2]=c;
x[3]=d;
x[4]=e;
x[5]=f;
}
}tr[maxn<<2];
Node merge_Node(Node a,Node b){
Node t;
t.len=a.len+b.len;
if(a.len%3==0){
t.x[0]=a.x[0]+b.x[0];
t.x[1]=a.x[1]+b.x[1];
t.x[2]=a.x[2]+b.x[2];
t.x[3]=a.x[3]+b.x[3];
t.x[4]=a.x[4]+b.x[4];
t.x[5]=a.x[5]+b.x[5];
}
else if(a.len%3==1){
t.x[0]=a.x[0]+b.x[1];
t.x[1]=a.x[1]+b.x[2];
t.x[2]=a.x[2]+b.x[0];
t.x[3]=a.x[3]+b.x[5];
t.x[4]=a.x[4]+b.x[3];
t.x[5]=a.x[5]+b.x[4];
}
else {
t.x[0]=a.x[0]+b.x[2];
t.x[1]=a.x[1]+b.x[0];
t.x[2]=a.x[2]+b.x[1];
t.x[3]=a.x[3]+b.x[4];
t.x[4]=a.x[4]+b.x[5];
t.x[5]=a.x[5]+b.x[3];
}
return t;
}
void build(int p,int l,int r){
if(l==r){
char c;
cin>>c;
if(c=='r'){
tr[p].x[0]=tr[p].x[3]=0;
tr[p].x[1]=tr[p].x[2]=tr[p].x[4]=tr[p].x[5]=1;
}
else if(c=='e'){
tr[p].x[1]=tr[p].x[4]=0;
tr[p].x[0]=tr[p].x[2]=tr[p].x[3]=tr[p].x[5]=1;
}
else {
tr[p].x[2]=tr[p].x[5]=0;
tr[p].x[0]=tr[p].x[1]=tr[p].x[3]=tr[p].x[4]=1;
}
tr[p].len=1;
return;
}
int mid=(l+r)>>1;
build(ls,l,mid);
build(rs,mid+1,r);
tr[p]=merge_Node(tr[ls],tr[rs]);
}
void print(int p,int l,int r){
printf("%d[%d,%d] ",p,l,r);
for(int i=0;i<6;i++)printf("%d ",tr[p].x[i]);
printf("\n");
if(l==r)return;
int mid=(l+r)>>1;
print(ls,l,mid);
print(rs,mid+1,r);
}
void print(){print(1,1,n);}
void mdy(int p,int l,int r,int idx,int v){
if(l==r){
tr[p].x[0]=tr[p].x[1]=tr[p].x[2]=tr[p].x[3]=tr[p].x[4]=tr[p].x[5]=1;
tr[p].x[v]=tr[p].x[v+3]=0;
return;
}
int mid=(l+r)>>1;
if(idx<=mid)mdy(ls,l,mid,idx,v);
else mdy(rs,mid+1,r,idx,v);
tr[p]=merge_Node(tr[ls],tr[rs]);
}
void mdy(int idx,char ch){
int t;
if(ch=='r')t=0;
else if(ch=='e')t=1;
else t=2;
mdy(1,1,n,idx,t);
}
Node qry(int p,int l,int r,int L,int R){
if(L<=l && r<=R){
return tr[p];
}
int mid=(l+r)>>1;
if(R<=mid)return qry(ls,l,mid,L,R);
else if(L>mid)return qry(rs,mid+1,r,L,R);
else return merge_Node(qry(ls,l,mid,L,R),qry(rs,mid+1,r,L,R));
}
int qry(int l,int r){
Node t=qry(1,1,n,l,r);
int minn=1e9;
for(int i=0;i<6;i++)minn=min(minn,t.x[i]);
return minn;
}
#undef ls
#undef rs
}tr;
int main(){
cin>>n>>Q;
tr.build(1,1,n);
while(Q--){
int op;
cin>>op;
if(op==1){
char ch;
int idx;
cin>>idx>>ch;
tr.mdy(idx,ch);
}
else {
int l,r;
cin>>l>>r;
cout<<tr.qry(l,r)<<endl;
}
}
return 0;
}