牛客小白月赛99题解(BFS、欧拉筛、完全背包、离散化、树状数组、二分查找)

牛客小白月赛99


康复训练


A. 材料打印(思维)

注意到两种打印方式的价格的大小关系。

#include<bits/stdc++.h>
#define ll long long 
using namespace std;

int main(){
	
	int ncase;
	cin >> ncase;
	
	while(ncase--){
		ll a, b, x, y, res;
		cin >> a >> b >> x >> y;
		if(x < y) res = a * x + b * y;
		else res = (a + b) * y;
		cout << res << endl;
	}
	
	
	return 0;
} 

B. %%%(思维,数论)

思路:

​ 通过简单的思考,n % 2 = 0 or 1n % 3 = 0,1,2n % 4 = 0,1,2,3,…

​ 观察上述规律,令 n = 2k + 1,则 n % (k+1) 时,取得最大值 k;令 n = 2k,则 n % (k+1)时,取得最大值 k-1。

#include<bits/stdc++.h>
#define ll long long 
using namespace std;

int main(){
	
	int ncase;
	cin >> ncase;
	
	while(ncase--){
		ll n;
		cin >> n;
		int count = 0;
		while(n){
			n = n % (n / 2 + 1);
			count++;
		}
		cout << count << endl;
	}
	
	return 0;
} 

C. 迷宫(BFS)

思路:

如果没有超能力,简单BFS,判断从S是否可以到E。

考虑超能力的使用,假设从S出发可以到达 (xS, yS),在使用超能力时,可以走到 xS 行或 yS列所在的所有点。

在从S出发BFS时,记录过程点所在的行和列。再从E出发BFS,判断是否可以走到第一遍BFS时记录的行或列。

#include<bits/stdc++.h>
#define ll long long 
using namespace std;

const int maxn = 1005;

typedef struct Node{
	int x;
	int y;
} node;

char ma[maxn][maxn];
int flag[maxn][maxn];
int row[maxn], column[maxn];

int opx[5] = {0, 0, 0, 1, -1};
int opy[5] = {0, 1, -1, 0, 0};

queue<node> qu;

int main(){
	
	int n, m;
	cin >> n >> m;
	
	int ex, ey;
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= m; j++){
			cin >> ma[i][j];
			if(ma[i][j] == 'S'){
				qu.push({i, j});
				flag[i][j] = 1;
			}
			if(ma[i][j] == 'E'){
				ex = i;
				ey = j;
			}
		}
	}
	// 不需要超能力走到 1,需要为 2 
	int res = 0;
	while(!qu.empty()){ // 从S开始BFS
		node now = qu.front();
		qu.pop();
		if(ma[now.x][now.y] == 'E'){
			res = 1;
			break;
		}
		if(now.x == ex || now.y == ey){
			res = 1;
			break;
		}
		row[now.x] = column[now.y] = 1; // 记录可以走到的行和列
		for(int i = 1; i <= 4; i++){
			int xx = now.x + opx[i];
			int yy = now.y + opy[i];
			if(xx < 1 || yy < 1) continue;
			if(xx > n || yy > m) continue;
			if(ma[xx][yy] == '#') continue;
			if(flag[xx][yy]) continue;
			flag[xx][yy] = 1;
			qu.push({xx, yy});
		}
	}
	
	qu.push({ex, ey});
	flag[ex][ey] = 2;
	while(res == 0 && !qu.empty()){ // 需要超能力的第二遍BFS
		node now = qu.front();
		qu.pop();
		int x = now.x, y = now.y;
		for(int i = 1; i <= 4; i++){
			int xx = now.x + opx[i];
			int yy = now.y + opy[i];
			if(xx < 1 || yy < 1) continue;
			if(xx > n || yy > m) continue;
			if(flag[xx][yy] == 2) continue;
			if(row[xx] || column[yy]){ // 使用超能力联通了
				res = 1;
				break;
			}	
			if(ma[xx][yy] != '#'){
				qu.push({xx, yy});
				flag[xx][yy] = 2;
			}
		}
	}
	
	cout << (res ? "YES" : "NO") << endl;
	
	
	return 0;
} 

D. 又是一年毕业季(欧拉筛)

思路:

找到没出现的最小的质数即可。

如果对欧拉筛有比较好的理解,是很容易理解的。从小到大,遍历a[i],不能被a[i] 及其倍数覆盖的第一个数,一定是一个素数。

#include<bits/stdc++.h>
#define ll long long 
using namespace std;

const int maxn = 2e5 + 5;

int a[maxn], b[maxn];

map<int, int> V;

int is_prime(int x){
	if(V[x] == 0){
		int flag = 1;
		for(int i = 2; i * i <= x; i++){
			if(x % i == 0){
				flag = -1;
				break;
			}
		}
		V[x] = flag;
	}
	return V[x] == 1;
}

int main(){
	
	int ncase;
	cin >> ncase;
	
	while(ncase--){
		int n;
		cin >> n;
		for(int i = 1; i <= n; i++) cin >> a[i];
		
		int m = 2*n+1;
		while(!is_prime(m)) m++;
		
		sort(a+1, a+1+n);
		for(int i = 1; i <= n; i++){
			int x = a[i];
			while(x <= m){
				b[x] = 1;
				x += a[i];
			}
		}
		int res = -1;
		for(int i = 2; i <= m; i++){
			if(b[i] == 0 && res == -1){
				res = i;
			}
			b[i] = 0;
		}
		cout << res << endl;
	}
	
	return 0;
} 

E. 多米诺骨牌(模拟)

思路:

按照骨牌摆放位置x排序,每次推到最左边,统计会倒几个牌。排序取数量最多的前m个。

#include<bits/stdc++.h>
#define ll long long 
using namespace std;

const int maxn = 1e6 + 5;

pair<int, int> a[maxn];

int main(){
	
	int ncase;
	cin >> ncase;
	
	while(ncase--){
		int n, m;
		cin >> n >> m;
		for(int i = 1; i <= n; i++) cin >> a[i].second;
		for(int i = 1; i <= n; i++) cin >> a[i].first;
		sort(a+1, a+1+n);
		vector<int> v;
		ll end = a[1].second + a[1].first, count = 1;
		for(int i = 2; i <= n; i++){
			ll len = a[i].second + a[i].first;
			if(a[i].first > end){
				v.push_back(count);
				end = len;
				count = 1;
			}
			else{
				end = max(end, len);
				count++;
			}
		}
		v.push_back(count); // 注意把最后一组放进去
		
		sort(v.begin(), v.end());
		
		int res = 0;
		for(int i = 1; i <= m; i++){
			if(i > v.size()) break;
			res += v[v.size()-i];
		}
		cout << res << endl;
	}
	
	return 0;
} 

F.自爆机器人(思维、完全背包)

思路:

机器人造成的伤害与时间有关,走最多的时间即可。

根据题意,机器人行走的时间T = 机器人从起始位置到怪物的位置n + 机器人在墙之间碰撞的时间S。

第一部分 ”机器人从起始位置到怪物的位置n“ 的时间是固定的,如果机器人可以在时间t之内走到n的话。

第二部分,机器人在墙之间碰撞,可以认为 A -> B -> A 是一组碰撞,这样一组碰撞的时间为 2 * (XB - XA),伤害为 2 * (XB - XA)。

显然,我们可以把m个墙,当作m-1个物品,每个物品的体积和价值均为 2 * (XB - XA);背包的容量为 t - n;背包可以装得下的最大的价值即为 S。

ps:这里,由于所有的墙都在 1 ~ n,且每个位置只有一个墙,故而最多有根号n个物品是不同的,注意去重。

时间复杂度:根号n * (t - n)

#include<bits/stdc++.h>
#define ll long long 
using namespace std;

const int maxn = 1e6 + 5;

ll a[maxn];
bool dp[maxn];

int main(){
	
	int ncase;
	cin >> ncase;
		
	while(ncase--){
		int n, m, t;
        cin >> n >> m >> t;
		for(int i = 1; i <= m; i++) cin >> a[i];
		if(n > t) cout << 0 << endl;
		else if(n == t) cout << n << endl;
		else{
			sort(a+1, a+1+m);
			set<ll> b;
			for(int i = 2; i <= m; i++) b.insert(2*(a[i] - a[i-1])); // 去重
			dp[0] = 1;
			for(auto x : b){
				for(int i = 0; i <= t-n; i++){
					dp[i] = dp[i] | dp[i - x];
				}
			}
			int flag = 1, res = n;
			for(int i = t-n; i >= 0; i--){
				if(flag && dp[i]){
					res += i;
					flag = 0;
				}
				dp[i] = 0;
			}
			cout << res << endl;
		}
	}
	
	return 0;
} 

G. 大鱼吃小鱼(离散化 、树状数组 、二分)

思路:

  1. 需要枚举每一个鱼出现时刻的端点,维护当前所有出现的鱼的重量。如果是左端点,则需要再模拟吃鱼的过程。

  2. 对鱼的重量进行离散化处理,用树状数组存储当前出现的鱼的重量。每次二分找能吃的最大重量的鱼,区间求合维护新重量。循环这个过程,直到没得吃。

    举个例子,如果当前x=10,池塘里有3,7,11,15....,==二分查找==小于等于x的第一个鱼,区间求和就可以得到当前能吃的鱼的最大总量
    
  3. 证明过程2的时间复杂度:

    	1. 当于某一个时刻,设小鱼的重量为 x,有一条小鱼不能吃的大鱼的重量为x+1,有一条小鱼可以吃的小小鱼的重量为1
    	2. 如果这时小鱼完成了一轮吃食(吃掉了小小鱼),重量变为x+1
    	3. 这时,小鱼就可以吃掉大鱼,重量变为 (x+1) * 2
    	4. 综上,每一次小鱼通过吃食,可以吃掉大鱼时,其重量最少*2。故而,大约32轮左右的吃食,小鱼的重量就会超过1e9,即可以吃掉所有鱼。
    
  4. 其他细节参见代码。

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int maxn = 2e5 + 10;

map<int, int> ID;
int ids = 0;
vector<pair<int, int> > pi;
vector<int> a;

ll tree[maxn];

void init(){
	ID.clear();
	pi.clear();
	a.clear();
	ids = 0;
}

int get_id(int x){	// 离散化
	if(ID[x] == 0) return ID[x] = ++ids;
	return ID[x];
}


int lowbit(int x){	
	return x & (-x);
}

void add(int x, int num){
	while(x < maxn){
		tree[x] += num;
		x += lowbit(x);
	}
}

ll query(int x){
	ll sum = 0;
	while(x){
		sum += tree[x];
		x -= lowbit(x);
	}
	return sum;
}

int main(){
	
	int ncase;
	cin >> ncase;
	while(ncase--){
		
		init();
		
		int n, x;
		cin >> n >> x;
		
		for(int i = 1; i <= n; i++){
			int l, r, k;
			cin >> l >> r >> k;
			pi.push_back({l, k});
			pi.push_back({r, -k});
			a.push_back(k);
		}
		
		sort(a.begin(), a.end());
		for(auto item : a) get_id(item); // 离散化
		
		sort(pi.begin(), pi.end());
		
		ll ans = 0;
		for(auto p: pi){						// 枚举每一个时刻
			int time = p.first, val = p.second;
			add(get_id(abs(val)), val);			// 维护池塘里现有的鱼
			if(val > 0){						// 有一个新鱼进入了 
				ll tmp = x;
				while(1){		// 循环这个过程
					int l = 0, r = a.size()-1, pos = -1;
					while(l <= r){				// 二分查找当前可以吃掉的最大的鱼
						int mid = l + r >> 1;
						if(a[mid] <= tmp){ 		
							l = mid + 1;
							pos = mid;
						}
						else{
							r = mid - 1;
						}
					}
					if(-1 == pos) break;		// 没有能吃的
					ll now_tmp = x + query(get_id(a[pos]));		// 区间求合
					if(now_tmp == tmp) break;	// 吃完没增加重量
					else tmp = now_tmp;
				}
				ans = max(ans, tmp);
			}
		}
		cout << ans << endl;
	}
	
	
	return 0;
} 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值