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

比赛链接

出题人是古希腊掌管BFS的神。

这场简单,CEF都可以用BFS来写。话说回来,常规做法是,B是二分查找,C考了同余的性质,D是推结论或者直接暴力,E是BFS或者dijkstra,F是状压DP。

每个题的解法感觉很多,这里只提供一种思路。


A 雾之湖的冰精

思路:

签到,两数之和大于9就no,否则yes

code:

#include <iostream>
#include <cstdio>
using namespace std;

int a,b;

int main(){
	cin>>a>>b;
	puts((a+b>9)?"No":"Yes");
	return 0;
}

B 博丽神社的巫女

思路:

这里注意一下每个巫女要的钱不是分开给的,而是一块给的,比如两个巫女分别要 3 , 4 3,4 3,4 块钱,那么我们只需要给 4 4 4 块钱,两个人就会开心,而不是给 7 7 7 块钱。

所以先把每个巫女想要的钱数进行一下排序,然后二分找到第一个小于等于 x x x 的位置 i d x idx idx 就好了。前面的巫女都会开心,因此有 i d x idx idx 巫女开心,剩下 x − a [ i d x ] x-a[idx] xa[idx] 块钱。

不二分直接从前到后跑一遍也可以。

code:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=1e5+5;

int n,x;
int a[maxn];

int main(){
	cin>>n>>x;
	for(int i=1;i<=n;i++)cin>>a[i];
	sort(a+1,a+n+1);
	int idx=upper_bound(a+1,a+n+1,x)-a-1;
	cout<<idx<<" "<<x-a[idx];
	return 0;
}

C 红魔馆的馆主

思路:

考虑到我们在一个数 a a a 结尾添加一个一位数 x x x,其实相当于 a ∗ 10 + x a*10+x a10+x,同理,添加两位数相当于 a ∗ 100 + x a*100+x a100+x,类推。

如果我们在 a a a 后面添加一个一位数 x x x 就凑出了 495 495 495 的倍数,说明 a ∗ 10 + x ≡ 0 ( m o d 495 ) a*10+x\equiv0\pmod{495} a10+x0(mod495) x = − a ∗ 10 ( m o d 495 ) x=-a*10\pmod{495} x=a10(mod495)因为 x x x 是一位数,所以我们要保证 − a ∗ 10   m o d   495 -a*10\bmod495 a10mod495 的结果 < 10 \lt 10 <10

同理,若 x x x 是两位数,要保证 − a ∗ 100   m o d   495 -a*100\bmod495 a100mod495 的结果 < 100 \lt 100 <100。若 x x x 是三位数,要保证 − a ∗ 1000   m o d   495 -a*1000\bmod495 a1000mod495 的结果 < 1000 \lt 1000 <1000,这时一定成立,因为模 495 495 495 的结果一定是小于 495 495 495 也就小于 1000 1000 1000

不过因为题目给的 a a a 1 0 18 10^{18} 1018 级别的,因此乘以 1000 1000 1000 后有可能爆掉 long long,所以可以用 __int128(不想用多取几次模也行)。注意补的是个数字串,所以可以带前导零。

同一个写法交了两遍,一个AC一个WA,怪起来了。AC WA

code:

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;

ll n;
int ans;

int main(){
	cin>>n;
	ll t=n;
	if(t%495==0)cout<<-1<<endl;
	else if((495+(-t)%495)*10%495<10){
		ans=(495+(-t)%495)*10%495;
		printf("%01d\n",ans);
	}
	else if((495+(-t)%495)*100%495<100){
		ans=(495+(-t)%495)*100%495;
		printf("%02d\n",ans);
	}
	else{
		ans=(495+(-t)%495)*1000%495;
		printf("%03d\n",ans);
	}
	return 0;
}

D 迷途之家的大贤者

思路1:

暴力的想法是我们直接模拟这个游玩过程,分别枚举小红小紫的子串的左右端点正好是 n 4 n^4 n4 的,不会超时。

考虑如何模拟这个游玩过程。因为小紫想要最后的子串最小,因此我们暴力枚举并删掉小红选择的子串,并在此基础上找到使得剩下的串字典序最小的删除方式。发现首先我们要保证最前面留下的是整个串里最小的字符,并在此基础上使得所有满足条件的串最小。所以我们有两种删除方式:

  1. 删掉后面的所有字符,只留下第一个字符。
  2. 删掉一个前缀,留下后面部分的字符。

直接暴力枚举每一个情况然后取出最小的情况即可。

因为小红想要最后的子串最大,所以我们枚举小红选择的子串后再在所有情况中取出剩下的最大的子串即可。

code1:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

int n;
string s,ans;

int main(){
	cin>>n>>s;
	s=" "+s;
	ans="";
	for(int l1=1;l1<=n;l1++)
		for(int r1=l1;r1<=n;r1++){
			if(l1==1 && r1==n)continue;
			string s1,s2;
			int n1;
			for(int i=1;i<=n;i++)	
				if(i<l1 || i>r1)
					s1+=s[i];
			n1=s1.length();
			s1=" "+s1;
			s2=s1[1];
			for(int i=1;i<n1;i++)
				s2=min(s2,s1.substr(i+1));
			ans=max(ans,s2);
		}
	cout<<ans<<endl;
	return 0;
}

思路2:

推结论,结论就是:开头字符和结尾字符取较大值。

为什么这是对的?回想上面小红删完之后小紫的策略,不妨假设小红先不删,对剩下的串,两种删除方式:

  1. 删掉后面的所有字符,只留下第一个字符。
  2. 删掉一个前缀,留下后面部分的字符。

所有可能的子串取最小的那个。

当小紫选择第一个策略的时候,说明最前面的字符就是整个串中最小的那个,那么我们小红就可以在删除子串的时候删掉最前面的字符。这时小紫改为通过第二种方式来删除,但是当小紫选择了某个位置,那么小红就可以选择删掉这个位置上的前缀,这样剩下的部分的字符一定大于等于当前位置的字符,答案会更好,直到剩下最后一个字符无法删掉。

或者小红可以选择一开始就删掉后面的部分,只留下第一个字符。所以最后只有两种结果,留第一个字符或者留最后一个字符。当然两者取较大值。

code2:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

int n;
string s;

int main(){
	cin>>n>>s;
	cout<<max(s.front(),s.back());
	return 0;
}

E 魔法之森的蘑菇

思路1:

还是比较容易看出来BFS做法的,不过每个位置需要多存储一维,也就是方向。因为当我们来到某个位置的时候,不同的方向可能导致后面的行走路线不同,但是vis数组多存储一维方向的话,如果方向也一致,那么后面所有可能的行走路线一定重复。

code1:

#include <iostream>
#include <cstdio>
#include <map>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;
const int maxn=1005;
const int inf=1e9;

int T,n,m;
string mp[maxn];

int sx,sy,ex,ey;
int fx[]={1,-1,0,0},fy[]={0,0,1,-1};

struct state{
	int x,y;
	int d;
	int dir;
	state(int x,int y,int d,int dir):x(x),y(y),d(d),dir(dir){};
};

int main(){
	cin>>T;
	while(T--){
		cin>>n>>m;
		for(int i=1;i<=n;i++){
			cin>>mp[i];
			mp[i]=" "+mp[i];
			for(int j=1;j<=m;j++)
				if(mp[i][j]=='S'){
					sx=i;
					sy=j;
				}
				else if(mp[i][j]=='T'){
					ex=i;
					ey=j;
				}
		}
		vector<vector<vector<bool> > > vis(n+5,vector<vector<bool> >(m+5,vector<bool>(5,false)));
		queue<state> q;
		q.push(state(sx,sy,0,4));
		bool flag=false;
		while(!q.empty()){
			int ux=q.front().x,uy=q.front().y,dir=q.front().dir,d=q.front().d,x,y;
			q.pop();
			if(vis[ux][uy][dir])continue;
			else vis[ux][uy][dir]=true;
			if(ux==ex && uy==ey){
				flag=true;
				cout<<d<<endl;
				break;
			}
			if(dir==4)
				for(int i=0;i<4;i++){
					x=ux+fx[i];
					y=uy+fy[i];
					if(x<1 || x>n || y<1 || y>m || mp[x][y]=='#' || vis[x][y][i])continue;
					
					if(mp[x][y]=='*')q.push(state(x,y,d+1,4));
					else q.push(state(x,y,d+1,i));
				}
			else {
				x=ux+fx[dir];
				y=uy+fy[dir];
				if(x<1 || x>n || y<1 || y>m || mp[x][y]=='#' || vis[x][y][dir])continue;
				
				if(mp[x][y]=='*')q.push(state(x,y,d+1,4));
				else q.push(state(x,y,d+1,dir));
			}
		}
		if(!flag)cout<<-1<<endl;
	}
	return 0;
}

思路2:

发现其实每个蘑菇只能到达上下左右第一个无障碍阻隔的蘑菇,中间的 . 都没用,这就相当于这个蘑菇到上下左右第一个无障碍阻隔的蘑菇有边相连。所以我们把每个蘑菇和起始终止位置看作一个点,然后根据图给点连边,问题就转化为了在图上跑最短路。

如果整个地图都是蘑菇,那么最多会有 n ∗ m = 1 0 6 n*m=10^6 nm=106 个点,每个点按连四条边算,需要连 4 ∗ 1 0 6 4*10^6 4106 条边,不会被卡掉。

code2:

#include <iostream>
#include <cstdio>
#include <map>
#include <cstring>
#include <queue>
#include <vector>
#define pii pair<int,int>
using namespace std;
const int maxn=1005;
const int inf=1e9;

int T,n,m;
string mp[maxn];
int num[maxn][maxn],nd;

int st,ed;
int fx[]={1,-1,0,0},fy[]={0,0,1,-1};

int head[maxn*maxn],ccc;
struct edge{
	int v,w,nxt;
}e[maxn*maxn<<2];
void add(int u,int v,int w){
	e[++ccc].v=v;
	e[ccc].w=w;
	e[ccc].nxt=head[u];
	head[u]=ccc;
}

void dijkstra(){
	vector<int> d(nd+5,inf);
	vector<bool> vis(nd+5,false);
	priority_queue<pii,vector<pii>,greater<pii> > h;
	h.push(pii(0,st));
	d[st]=0;
	while(!h.empty()){
		int u=h.top().second;
		h.pop();
		if(vis[u])continue;
		else vis[u]=true;
//		cout<<u<<endl;
		if(u==ed){
			cout<<d[u]<<endl;
			return;
		}
		
		for(int i=head[u],v,w;i;i=e[i].nxt){
			v=e[i].v;
			w=e[i].w;
			if(d[v]>d[u]+w){
				d[v]=d[u]+w;
				h.push(pii(d[v],v));
			}
		}
	}
	cout<<-1<<endl;
	return;
}

int main(){
	cin>>T;
	while(T--){
		cin>>n>>m;
		for(int i=1;i<=n;i++){
			cin>>mp[i];
			mp[i]=" "+mp[i];
			for(int j=1;j<=m;j++){
				if(mp[i][j]=='S'){
					num[i][j]=++nd;
					st=nd;
				}
				else if(mp[i][j]=='T'){
					num[i][j]=++nd;
					ed=nd;
				}
				else if(mp[i][j]=='*'){
					num[i][j]=++nd;
				}
			}
		}
		
		for(int i=1,x,y,cnt;i<=n;i++)
			for(int j=1;j<=m;j++){
				if(mp[i][j]=='.' || mp[i][j]=='#')continue;
				for(int k=0;k<4;k+=2){
					x=i;y=j;cnt=0;
					do{
						x+=fx[k];
						y+=fy[k];
						cnt++;
					}while(x<=n && y<=m && mp[x][y]=='.');
					if(x>n || y>m || mp[x][y]=='#')continue;
					add(num[i][j],num[x][y],cnt);
					add(num[x][y],num[i][j],cnt);
				}
			}
		
		dijkstra();
		
		for(int i=1;i<=nd;i++)head[i]=0;
		nd=0;ccc=0;
	}
	return 0;
}

F 三途川的摆渡人

思路1:

多个数与运算的时候,对某一位二进制位,如果有一个数在这一位上是 0 0 0,那么结果这一位上就是 0 0 0。因此我们选中的若干个数里,每一位二进制位上都至少存在一个数,在这一位上为 0 0 0

假如我们已经选了某些数,它们的与的结果仍然有一些二进制位为 1 1 1,那么我们就可以选择某些数来尝试将这一位置为 0 0 0,然后转移到一个新的与的结果,这有点像状态转移,这意味着我们可以用动态规划解决这个问题。

d p [ i ] dp[i] dp[i] 表示与的结果为 i i i 时最少选择的数的数量。对某个数 a j a_j aj,就可以转移到 d p [ i   &   a j ] dp[i\,\&\,a_j] dp[i&aj] 了。一开始没有选数的时候,我们假设所有二进制位都是 1 1 1。我们要找的答案就是 d p [ 0 ] dp[0] dp[0]

能否保证一个数只用一次?因为多次与上一个数,结果是不会变的。之后再用这个数的时候,与出来的状态就是这个状态本身,不可能更新答案。所以一个数只有可能使用一次。

因为与出来的状态一定是减小的,比某个状态更大的状态是不会改变的,所以我们从最大的状态从大到小枚举即可,这有点像 01 01 01 背包的处理方式。

这算数位DP还是状压DP?

code1:

#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
const int maxn=2e5+5;
const int inf=1e9;

int T,n,a[maxn];

int main(){
	cin>>T;
	while(T--){
		cin>>n;
		for(int i=1;i<=n;i++)cin>>a[i];
		vector<int> dp(300,inf);
		dp[(1<<8)-1]=0;
		for(int i=(1<<8)-1;i>0;i--){
			for(int j=1;j<=n;j++){
				dp[i&a[j]]=min(dp[i&a[j]],dp[i]+1);
			}
		}
		if(dp[0]==inf)cout<<-1<<endl;
		else cout<<n-dp[0]<<endl;
	}
	return 0;
}

思路2:

其实上面的动态规划做法本质上就是状态的转移,而我们把状态看成一个点,而每个点通过一个数转移到下一个点的边权都是 1 1 1,那么就可以BFS解决了。

因为每个数至少会将一位二进制位置为 0 0 0,因此答案最多为 8 8 8,而且有 v i s vis vis 数组,不会超时。

code2:

#include <iostream>
#include <cstdio>
#include <vector>
#include <queue>
#define pii pair<int,int>
using namespace std;
const int maxn=2e5+5;
const int inf=1e9;

int T,n,a[maxn];

int main(){
	cin>>T;
	while(T--){
		cin>>n;
		for(int i=1;i<=n;i++)cin>>a[i];
		
		vector<bool> vis((1<<8),false);
		queue<pii> q;//状态,步数 
		bool flag=false;
		q.push(pii((1<<8)-1,0));
		while(!q.empty()){
			int st=q.front().first,step=q.front().second;
			q.pop();
			if(vis[st])continue;
			else vis[st]=true;
			if(st==0){
				cout<<n-step<<endl;
				flag=true;
				break;
			}
			for(int i=1;i<=n;i++){
				q.push(pii(st&a[i],step+1));
			}
		}
		if(!flag)cout<<-1<<endl;
	}
	return 0;
}
  • 13
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值