文章目录
康复训练
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 1,n % 3 = 0,1,2 ,n % 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. 大鱼吃小鱼(离散化 、树状数组 、二分)
思路:
-
需要枚举每一个鱼出现时刻的端点,维护当前所有出现的鱼的重量。如果是左端点,则需要再模拟吃鱼的过程。
-
对鱼的重量进行离散化处理,用树状数组存储当前出现的鱼的重量。每次二分找能吃的最大重量的鱼,区间求合维护新重量。循环这个过程,直到没得吃。
举个例子,如果当前x=10,池塘里有3,7,11,15....,==二分查找==小于等于x的第一个鱼,区间求和就可以得到当前能吃的鱼的最大总量
-
证明过程2的时间复杂度:
1. 当于某一个时刻,设小鱼的重量为 x,有一条小鱼不能吃的大鱼的重量为x+1,有一条小鱼可以吃的小小鱼的重量为1 2. 如果这时小鱼完成了一轮吃食(吃掉了小小鱼),重量变为x+1 3. 这时,小鱼就可以吃掉大鱼,重量变为 (x+1) * 2 4. 综上,每一次小鱼通过吃食,可以吃掉大鱼时,其重量最少*2。故而,大约32轮左右的吃食,小鱼的重量就会超过1e9,即可以吃掉所有鱼。
-
其他细节参见代码。
#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;
}