蓝桥杯题单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;
}
posted @ 2023-05-01 18:47  Sakana~  阅读(264)  评论(4编辑  收藏  举报