牛客是个好东西(),之前打寒假练习的时候就发现上面对对应的专题设置了板块练习,非常适合慢慢刷。这周抽空刷了状压枚举和二分法的题。同时尝试给自己相对较长的时间去独立解题,感觉效果还不错,具体在题目里一一道来。
Bob的蛋糕店
题目链接:https://www.nowcoder.com/practice/f4f6870953b64e059fdb5f76dc69b46d?channelPut=tracker3
数据很小,是道练习状压枚举的模板题。
大致题意:在横轴上有个质点,第个质点的质量为,横坐标为,支点位于全部质点的质心 ,随后拿走个质点,判断剩余质点的质心是否依旧位于质点处,是输出,否则输出。
思路:我们直接枚举取走的所有情况然后计算判断质点是否改变即可。
本题使用了来将拿走第位设置为在对应的位设置为,不取设置为,(的第一位是)来对于种情况逐一枚举判断是否满足条件。这里也可以不用,而是使用位运算操作来逐位取。
NOTE状压枚举:将问题转化成为来进行位操作,减少开数组等其他方式的空间开销。对于数据相对较小和比较复杂的实现可以简化。
#include <bits/stdc++.h>
using namespace std;using ll = long long;#define endl '\n'#define mod 998244353
int m[30];
void solved(){ int n, k; cin >> n >> k; int sum1 = 0; int sum2 = 0;
for (int i = 1; i <= n; i++){ cin >> m[i]; sum1 += m[i] * i; sum2 += m[i]; } double zx = sum1 * 1.0 / sum2; for (int i = 1; i <= (1 << n) - 1; i++){ bitset<25> bits(i); int sum1 = 0; int sum2 = 0; if(bits.count() == k){ for (int i = 0; i < n;i++){ if(bits[i] == 1){ sum1 += m[i + 1] * (i + 1); sum2 += m[i + 1]; } } } double cd = sum1 * 1.0 / sum2; if(cd == zx){ cout << "Yes" << endl; return; } } cout << "No" << endl;}
int main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); solved(); return 0;}小红的扫雷游戏
题目链接:https://www.nowcoder.com/practice/e610b4dac2c747a09b926053df59c958?channelPut=tracker3
这题思路还是比较明确的,我们依旧是通过状压枚举来判断这些格子是否可能为雷。姑且想的出思路,不过实现还真是比较复杂的。
大致题意:给定一个 的区域,其中有一些位置已经被翻开,需要根据已有信息判断哪些一定是雷,哪些一定不是雷。对于一个确定的格子,如果是雷就标记为,不是雷则标记为。不确定的格子保留为,已经确定的数字也按数字输出。
思路:我们只需要枚举有数字的周围八格的所有组成情况,最坏情况总数不会超过,依旧非常小。如果枚举的合法条件的一个格子有两种情况,即可能是雷也可能不是雷则在输出时改为。其他格子情况一致的正常输出或。
代码实现主要也是将未知的格子进行状压枚举,只不过将压缩的状态转化为格子状态和判断的时候比较复杂,详细请看注释。
#include <bits/stdc++.h>
using namespace std;using ll = long long;#define endl '\n'#define mod 998244353typedef pair<int, int> pii;
vector<string> grid(5);
vector<pii> blanks; //收集未知格子"."的坐标
int dx[8] = {1, 1, 1, 0, -1, -1, -1, 0};int dy[8] = {1, 0, -1, -1, -1, 0, 1, 1};
void solved(){ for (int i = 1; i <= 4;i++){ cin >> grid[i]; grid[i] = " " + grid[i]; }
// 保留原始棋盘 vector<string> orig = grid;
for(int i = 1; i <= 4;i++){ for (int j = 1; j <= 4; j++){ if(grid[i][j] == '.'){ blanks.push_back({i, j}); } } } int cnt = blanks.size();
vector<int> state(cnt, -1); //-1为初始,0为"O", 1为"X",2为不确定
//对于所有未知格子我们填入当前情况下的对应的确定值 for (int i = 0; i <= (1 << cnt) - 1; i++){ grid = orig; bitset<20> bits(i); for (int b = 0; b < cnt; b++){ if(bits[b] == 1){ grid[blanks[b].first][blanks[b].second] = 'X'; } if(bits[b] == 0){ grid[blanks[b].first][blanks[b].second] = 'O'; } } bool f = true; //判断这个组合是否合法(满足数字对周围格子的雷数量的要求) for (int j = 1; j <= 4 && f; j++){ for (int k= 1; k <= 4 && f; k++){ if (grid[j][k] - '0' >= 0 &&grid[j][k] - '0' <= 9) { int cnt1 = 0; for (int l = 0; l <= 7; l++){ int x = j + dx[l]; int y = k + dy[l]; if(x >= 1 && x <= 4 && y >= 1 && y <= 4 && grid[x][y] == 'X'){ cnt1++; } } if(cnt1 != grid[j][k] - '0'){ f = false; } } } } //如果合法就计入state用以判断最终结果 if(f == true){ bitset<20> bits(i); for (int b = 0; b < cnt; b++){ if(bits[b] == 1){ grid[blanks[b].first][blanks[b].second] = 'X'; } else if(bits[b] == 0){ grid[blanks[b].first][blanks[b].second] = 'O'; } if(state[b] == -1){ state[b] = bits[b]; } else if(state[b] != bits[b]){ state[b] = 2; } } } }
vector<string> ans = orig; //记录结果,并按照state的状态修改对应的格子
//对于未确定的格子判断(都存在blanks里) for (int b = 0; b < cnt; b++){ int x = blanks[b].first, y = blanks[b].second; if(state[b] == 1){ ans[x][y] = 'X'; } else if(state[b] == 0){ ans[x][y] = 'O'; } else{ ans[x][y] = '.'; } }
for (int i = 1; i <= 4; i++){ for (int j = 1; j <= 4; j++){ cout << ans[i][j]; } cout << endl; }}
int main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); solved(); return 0;}整数域二分
题目链接:https://www.nowcoder.com/practice/ee70e343d1bf4646b6eace9957f697c9?channelPut=tracker3
正如其名,是一道模板,我独立解题时主要是搞错了数据的范围。应该是数组下标作最大值而不是数组元素的最大值。
大致题意:给长度为的数组,每次操作输出数组中大于且小于等于的元素个数。一共需要操作次。
思路:我们利用二分查找分别找到最小的左端位置和最大的右端位置即可。
二分法就是我们中学数学提到的折半查找(注意二分要满足单调性),应该不用过多赘述了。这里我们可能要注意一下我们搜索的是什么,最大值应该如何取,还有对应搜索的应该尽可能左搜还是右搜。
#include <bits/stdc++.h>
using namespace std;using ll = long long;#define end '\n'typedef pair<int,int> pii;
ll a[200010];
ll l, r;
ll check1(ll x){ if(a[x] >= l){ return true; } return false;}ll check2(ll x){ if(a[x] <= r){ return true; } return false;}void solved(){ int n, q; cin >> n >> q;
for (int i = 1; i <= n;i++){ cin >> a[i]; }
sort(a + 1, a + 1 + n);
while(q--){ ll index1 = 1, index2 = n; cin >> l >> r; ll left = 1, right = n;
//二分找左边端点 while(left <= right){ ll mid = left + (right - left) / 2; if(check1(mid)){ index1 = mid; right = mid - 1; //尽可能左搜 } else{ left = mid + 1; } } left = 1, right = n;
//二分找右边的端点 while(left <= right){ ll mid = left + (right - left) / 2; if(check2(mid)){ index2 = mid; left = mid + 1; //尽可能右搜 } else{ right = mid - 1; } } cout << index2 - index1 + 1 << endl; }}
int main(){ ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); solved(); return 0;}不想做二分题
题目链接:https://www.nowcoder.com/practice/3732bf4e88704ab49b8010f42edc7e63?channelPut=tracker3
也算是一类经典的贪心+二分的题目了,独立解题的时候主要是卡在了不知道怎么思考贪心的实现上。确实要多练了。。。
大概题意:一排上有个怪,每回合都可以进行一次所有怪扣一滴血的,和选择一个单体扣一滴血,和一次对相邻的两个怪各扣一滴血(可对死去的怪使用)。求击杀所有怪物最少需要多少回合。
思路:我们使用二分答案,每次都对一个答案进行贪心判断是否能击杀所有怪,能就继续向左搜找最小满足条件的回合数。
这里贪心的思路是先全体,因为能够打到最多的单位。其次是双打。双打要保证不存在相邻的怪存活才能最优解化,所以我们双打的时候是两两进行取最小值来打。最后是记录还没被击杀的怪物的血量,看点杀的次数够不够。这里注意,记录了双打时是否还有次数剩余,有的话我们可以用来点杀(题目说可以对失去的怪物释放技能。)
#include <bits/stdc++.h>
using namespace std;using ll = long long;#define endl '\n'#define mod 998244353typedef pair<int, int> pii;
const double pi = 3.14159265358;
ll a[200010];ll b[200010];int n;
ll check(ll x){ //优先全体AOE for (int i = 1; i <= n; i++){ b[i] = a[i]; b[i] = max(0LL, b[i] - x); } ll k = 0; //记录双打的使用次数。 //接着是双打 for (int i = 1; i < n;i++){ ll d = min(b[i], b[i + 1]); //每次都保证选择的两者其中有一个被击杀 d = min(d, x - k); b[i] -= d; b[i + 1] -= d; k += d; if(k == x) break; } //最后逐一点杀 ll final_blood = 0; for (int i = 1; i <= n;i++){ final_blood += b[i]; } return final_blood <= 2 * x - k; //没使用完的双打可以用来点杀}
void solved(){ cin >> n; ll ma = 0; for (int i = 1; i <= n;i++){ cin >> a[i]; if(ma < a[i]){ ma = a[i]; } } ll ans = ma; ll left = 1, right = ma; while(left <= right){ ll mid = left + (right - left) / 2; if(check(mid)){ ans = mid; right = mid - 1; } else{ left = mid + 1; } } cout << ans << endl;}
int main(){ ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); solved(); return 0;}