牛客周赛 Round 36(A,B,C,D,E,F)

比赛链接

这场简单,只有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 小红的小红走矩阵

思路:

构造题,这几个限制条件说的其实就是:

  1. 答案路径不能遍历整个图。
  2. 答案路径必须拐个弯,不能只靠向左向下就行。
  3. 某个字母不能出现超过一半的位置。
  4. 必须存在答案路径。

那我们的构造可以分成两种思路,一种是先让答案路径必须遍历整个图,然后再开辟一条近路,一种是先让答案路径可以简单的走到最下面,然后再走到最右边,然后再在必经之路上卡住,让答案路径绕一下远路。直觉上第二种更简单,因此尝试第二种。

让路径一直向下走到头而不从中间跑出去,我们可以让相邻两行不同,然后第二列和第一列保持一致。走到最下边后,想要不能只向右走,可以在某个相邻两个位置放上一对相同的字符,这样答案就必须向上绕路。这样构造出来了,其他位置无所谓,为了不出现某个字符很多的情况,可以在没必要的位置用 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 种情况。即 rededrdrerdedererd。如果用 012 012 012 分别代表 r e d red red,那么就是 012120201021210102。把一个字符串修改为满足条件的串,就相当于把这个串修改成上述的任意一种的串。那么这个题的操作 2 2 2 就变成了统计把一段子串修改为上述六种其一的串的最少修改次数。

因为要在 l o g log log 时间内对某个区间的信息进行统计,考虑线段树维护。但是两个区间也就是串的合并规则并不明确。

考虑如何将两个串合并。发现 012120201 相当于一个串的循环,同理 021210102。如果一个 012 类型的串后面接上另外一个串仍然保持 012 类型,那么就需要看前面那个串的长度。如果前面的串模 3 3 3 有余数的话,这个多出来的一小部分相当于给后面的串补了一点点,比如余 1 1 1,前面的串相当于给后面的串补了一个最前面的 0 0 0,后续只需要循环补上 120 120 120 就行了。于是有:

  1. 如果长度能被 3 3 3 整除,那么后面就接 012 012 012 类型。
  2. 如果长度能除 3 3 3 余 1 1 1,那么后面就接 120 120 120 类型。
  3. 如果长度能除 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;
} 
相关推荐
Yang.991 小时前
基于Windows系统用C++做一个点名工具
c++·windows·sql·visual studio code·sqlite3
熬夜学编程的小王1 小时前
【初阶数据结构篇】双向链表的实现(赋源码)
数据结构·c++·链表·双向链表
zz40_1 小时前
C++自己写类 和 运算符重载函数
c++
六月的翅膀2 小时前
C++:实例访问静态成员函数和类访问静态成员函数有什么区别
开发语言·c++
liujjjiyun2 小时前
小R的随机播放顺序
数据结构·c++·算法
¥ 多多¥2 小时前
c++中mystring运算符重载
开发语言·c++·算法
天若有情6733 小时前
c++框架设计展示---提高开发效率!
java·c++·算法
Root_Smile3 小时前
【C++】类和对象
开发语言·c++
Reese_Cool3 小时前
【数据结构与算法】排序
java·c语言·开发语言·数据结构·c++·算法·排序算法