蓝桥杯题单day1【题解】
蓝桥杯题单day1
题目
https://www.cnblogs.com/CTing/p/17377395.html
题解
P1162 填涂颜色
思路
相当于从外围开始染色(如图所示,红框中是要染色的部分),把 \(1\) 以外的染色之后,就把 \(1\) 内外的 \(0\) 区分开来了。
注意边界,要往外多扩一层,不然就会出现“四角被隔断”(如下图)的情况,导致有一些角落没能被标记
(原图中四角不连通,往外扩一层即可联通)
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 35;
int a[N][N], n;
int dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1};
bool Range (int x, int y) {
if (x < 0 || x > n + 1 || y < 0 || y > n + 1) return false; //相当于最外层再围一圈1
return true;
}
void dfs (int x, int y) {
for (int i = 0; i < 4; i++) {
int xx = x + dx[i], yy = y + dy[i];
if (!Range (xx, yy) || a[xx][yy]) continue;
a[xx][yy] = 3;
dfs (xx, yy);
}
}
int main () {
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cin >> a[i][j];
}
}
dfs (1, 1);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (a[i][j] == 0) a[i][j] = 2;
else if (a[i][j] == 3) a[i][j] = 0;
cout << a[i][j] << ' ';
}
cout << endl;
}
}
//把1外染色
//注意边界
P1378 油滴扩展
思路
最多只有 \(6\) 个点,所以可以直接暴力枚举所有点的摆放顺序,时间复杂度为 \(O(n!)\)。
对于当前枚举到的点 \(i\),可以选择放或是不放。若放的话,利用 \(count()\) 函数统计该点最大能扩展到的半径,总复杂度为 \(O(n\cdot n!)\)。
Code
#include <bits/stdc++.h>
#define db double
#define pi acos (-1)
using namespace std;
const int N = 10;
int sx, fx, sy, fy;
int vis[N], n;
db r[N], ans;
struct Node {
int x, y;
}e[N];
db count (int x) {
db R = min ({abs (sx - e[x].x), abs (fx - e[x].x), abs (sy - e[x].y), abs (fy - e[x].y)});
for (int i = 1; i <= n; i++) {
if (i == x || !vis[i]) continue; //未放置
db dis = sqrt ((e[x].x - e[i].x) * (e[x].x - e[i].x) + (e[x].y - e[i].y) * (e[x].y - e[i].y));
R = min (R, max (0.0, dis - r[i]));
}
return R;
}
void dfs (int x, db sum) { //已经放了x个油滴,总面积为sum
if (x > n) {
ans = max (ans, sum);
return ;
}
for (int i = 1; i <= n; i++) {
if (vis[i]) continue; //放过了
vis[i] = true;
r[i] = count (i);
dfs (x + 1, sum + pi * r[i] * r[i]);
vis[i] = false; //回溯
}
}
int main () {
cin >> n;
cin >> sx >> sy >> fx >> fy;
for (int i = 1; i <= n; i++) cin >> e[i].x >> e[i].y;
dfs (1, 0);
cout << (int)(abs (sx - fx) * abs (sy - fy) - ans + 0.5) << endl;
}
P8644 [蓝桥杯 2016 国 B] 机器人塔
思路
正着考虑的话,摆放有多种可能。如果倒着从下往上放,则只要确定了最后一行整体就确定了。
所以,直接二进制枚举最后一行,复杂度为 \(O(2^n)\),其中 \(n\) 为层数,可通过 \(\frac{n(n+1)}2=a+b\) 计算出。
什么是二进制枚举?
就是对于每一位只有两种可能的情况,对应用 0/1 来表示选/不选,利用二进制数来运算
关于二进制枚举 关于位运算
枚举完最后一行的状态之后就是一个从下往上计算的过程,统计当前行为止的 \(A,B\) 摆放个数,若超出了规定个数则进行可行性剪枝,直接返回 \(false\)。利用本行倒推上一行的摆放状态时用到了异或的性质,观察发现:
AA/BB -> A, AB/BA -> B
用 \(0\) 表示 \(A\),\(1\) 表示 \(B\),就有:
00/11 -> 0, 01/10 -> 1(xor)
恰好对应异或\((xor)\) 的操作
Code
#include <bits/stdc++.h>
using namespace std;
int a, b, n, ans;
bool count (int st) {
int cnta = 0, cntb = 0;
for (int i = n - 1; i >= 0; i--) { //第i层
// cout << st << ' ';
for (int j = 0; j <= i; j++) { //第j个
if (st >> j & 1) cntb ++;
else cnta ++;
}
//cout << st << ' ' << cnta << ' ' << cntb << endl;
if (cnta > a || cntb > b) return false;
int cur = 0;
for (int j = i - 1; j >= 0; j--) {
int l = st >> j & 1, r = st >> (j + 1) & 1;
cur <<= 1, cur += (l ^ r);
}
st = cur; //更新上一层状态
}
// cout << endl;
return (cnta == a && cntb == b);
}
int main () {
cin >> a >> b;
n = sqrt ((a + b) * 2); //层数
//二进制枚举最后一行的放置情况
for (int i = 0; i < (1 << n); i++) { //0表示放A, 1表示放B
if(count (i)) ans ++;
}
cout << ans << endl;
}
//n*(n+1)/2 < 231 -> n < 21
//AA/BB -> A, AB/BA -> B (xor)
移动字母
思路
分析可得,最多有 \(6!\) 种情况(其中还包括不可能达到的局面)因此直接爆搜即可。
首先考虑如何储存当前搜索的状态:
采用“展开”的方式,将 \(n\times m\) 的矩阵转化为一维字符串。
(x, y) -> st = x * m + y
由该状态还原坐标只需:
st -> x = st / m, y = st % m
然后再利用 \(map\) 映射就对应上了字符串。
每次取出 \(*\) 的坐标,将与上下左右相邻格子进行交换。
Code
#include <bits/stdc++.h>
using namespace std;
int dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1};
string st = "ABCDE*";
bool Range(int x, int y) {
if (x < 0 || x >= 2 || y < 0 || y >= 3) return false;
return true;
}
int bfs(string ed) {
queue<string> q;
map<string, int> dis; //map 映射表示 <状态,距离>
q.push(st), dis[st] = 0;
while (!q.empty()) {
string t = q.front();
q.pop();
int dist = dis[t];
if (t == ed) return 1; // 达到目标状态
int k = t.find('*'), x = k / 3, y = k % 3; // 空格坐标
for (int i = 0; i < 4; i++) {
int xx = x + dx[i], yy = y + dy[i];
if (!Range(xx, yy)) continue;
//枚举空格移位
swap(t[k], t[xx * 3 + yy]);
if (dis.count(t) == 0) { // 没达到过状态t
dis[t] = dist + 1;
q.push(t);
}
swap(t[k], t[xx * 3 + yy]); // 回溯
}
}
return 0; //达不到目标状态
}
int main() {
int t;
cin >> t;
while (t--) {
string s;
cin >> s;
cout << bfs (s) << endl;
}
}
P8647 [蓝桥杯 2017 省 AB] 分巧克力
思路
易发现,最大可能的边长具有单调性(即若答案 \(x\) 满足要求,则 \(x-1\) 也满足)。因此可以利用二分求解, \(check\) 函数中判断 \(n\) 块巧克力能否切出至少 \(k\) 块 \(x\times x\) 的小巧克力。(注意不能拼凑,只能整块整块切)
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int h[N], w[N], n, k;
bool check (int x) {
int cnt = 0;
for (int i = 1; i <= n; i++) cnt += (h[i] / x) * (w[i] / x);
return cnt >= k;
}
int main () {
cin >> n >> k;
for (int i = 1; i <= n; i++) cin >> h[i] >> w[i];
int l = 1, r = 1e9;
while (l < r){
int mid = l + r + 1 >> 1;
if (check (mid)) l = mid;
else r = mid - 1;
}
cout << l << endl;
}
[ABC144E] Gluttony
思路
无修改时,考虑怎么安排使得最大值最小。则将 \(A\) 升序排序,\(F\) 降序排序或反过来有最大值最小。贪心证明:
有 \(A_i\geq A_j, F_i\geq F_j\),则 \(max\{ A_i\times F_j, A_j\times F_i \}\leq max\{ A_i\times F_i, A_j\times F_j \}\),因此要错位排序。
接下来考虑修改,发现若修改 \(k\) 次后满足要求的最大值为 \(x\),则 \(x+1\) 也满足(只会使得最大值更小),因此二分最大值。\(check\) 函数中对于每对超过 \(x\) 的 \(A_i\) 贪心修改 \(\lceil \frac{A_i\times F_i-x}{F_i} \rceil\) 次(注意要上取整),看总修改次数是否超过 \(k\)。
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2e5 + 5;
ll n, k, a[N], b[N];
bool check (ll x) {
//改a能否在k次之内使得最大值<=x
ll cnt = 0;
for (int i = 1; i <= n; i++) {
if (a[i] * b[i] <= x) continue;
cnt += (a[i] * b[i] - x + b[i] - 1) / b[i]; //统计差值贡献
}
return cnt <= k;
}
int main () {
cin >> n >> k;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) cin >> b[i];
//贪心组合出最小方案
sort (a + 1, a + n + 1), sort (b + 1, b + n + 1, greater<>());
ll l = 0, r = 0;
for (int i = 1; i <= n; i++) r = max (r, a[i] * b[i]);
while (l < r) {
ll mid = l + r >> 1;
if (check (mid)) r = mid;
else l = mid + 1;
}
cout << r << endl;
}
//二分
[ABC153F] Silver Fox vs Monster
思路
贪心。覆盖范围转化为 \([x_i,x_i+2d]\)。(因为从左往右考虑的话,把炸弹放在左端点的对后续覆盖的贡献更大)
然后炸弹起作用是对区间的一段数统一修改,这恰好符合差分的性质。用差分数组 \(b_i\) 表示考虑前 \(i\) 个怪物时,要放多少个炸弹。
然后二分最大覆盖范围,差分更新区间贡献。
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
typedef pair<ll, ll> pii;
const int N = 2e5 + 5;
ll n, d, a, cnt, b[N]; //差分数组
pii p[N];
int main () {
cin >> n >> d >> a;
for (int i = 1; i <= n; i++) {
cin >> p[i].first >> p[i].second;
p[i].second = (p[i].second + a - 1) / a; //转化为需要炸几次
}
sort (p + 1, p + n + 1);
for (int i = 1; i <= n; i++) {
//找到第一个能被消去的
b[i] += b[i-1];
ll cur = p[i].second - b[i];
if (cur <= 0) continue;
cnt += cur, b[i] += cur; //在第i个boss所在地放了cur个炸弹
//在i上放了cur个炸弹之后二分以i为左端点能覆盖到的最大范围
ll l = i, r = n;
while (l < r) {
int mid = l + r + 1 >> 1;
if (p[mid].first <= p[i].first + 2 * d) l = mid;
else r = mid - 1;
}
b[l+1] -= cur; //[i,l]都-cur
}
cout << cnt << endl;
}
//贪心把炸弹放在xi, 则覆盖[xi,xi+2d]
//差分维护改变值, 二分最大范围
//优化: 差分 + 二分
P8660 [蓝桥杯 2017 国 A] 区间移位
思路
先对原区间进行排序,优先将右端点升序排序,若右端点相等将左端点升序排序。
遍历区间时,求出从左往右第一个符合条件(即还没有计算过且可以被覆盖的)的下标
然后更新到目前为止最远可以覆盖到的右端点。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 10005;
bool vis[N];
int n;
struct Node {
int l, r;
bool operator<(const Node &t) const {
if (t.r != r) return r < t.r;
return l < t.l;
}
} p[N];
bool check(double x) { //浮点数比较是为了直接四舍五入
x *= 0.1;
double ans = 0; //最右覆盖范围
memset(vis, false, sizeof(vis));
for (int i = 1, j = 1; i <= n && ans < 10000; j = i) {
while (j <= n && (vis[j] || p[j].l > x + ans)) j++; // 找到第一个符合的
if (j > n) return false; //找不到
vis[j] = true; // 代表移动过
if (ans - p[j].l >= x) ans = max(ans, p[j].r + x); //右边可以延伸
else ans += p[j].r - p[j].l; // 右边不能延伸
if (i == j) i++;
}
return ans >= 10000;
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> p[i].l >> p[i].r;
sort(p + 1, p + n + 1);
int l = 0, r = 100000;
while (l < r) {
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
cout << r * 0.1 << endl;
}