湘南学院ACM暑期排位赛第四场题解报告(D、E)

前言

个人认为,本场比赛 E E E 题比 D D D 题简单一点,也好讲一点。所以我们先将 E E E 题再讲 D D D 题。

E - Sinking Land

题目大意:

你有一个大小为 n × m n \times m n×m 的岛屿,其中在第 i i i 行,第 j j j 列地区的海拔为 a i , j a_{i, j} ai,j,岛屿四面环海,岛屿的面积即为各区域面积之和,每个区域面积为 1 1 1

从现在开始,海平面每年上升 1 1 1。当海平面大于等于地区的海拔高度时,所有临海的地区都会被淹没。

求·对于每个 i = 1 , 2 , 3 , 4 , . . . , Y i = 1, 2, 3, 4, ..., Y i=1,2,3,4,...,Y,求 i i i 年后,该岛屿的仍高于海平面的面积。

数据范围: 1 ≤ n , m ≤ 1000 1 \leq n, m \leq 1000 1n,m1000 1 ≤ Y ≤ 1 0 5 1 \leq Y \leq 10^5 1Y105 1 ≤ a i , j ≤ 1 0 5 1 \leq a_{i, j} \leq 10^5 1ai,j105

分析

1、只有临海地区的海拔低于等于海平面时,该地区才会被淹没。而初始时,只有第一行、第 n n n 行、第一列、第 m m m 列是临海的。所以我们将这些数据的存起来,我用的是 m a p map map,其键是地区的海拔,值则是一个 v e c t o r vector vector 里面存放的是同样是这个海拔的且临海的地区的坐标。

2、因为海平面是逐一上升的,所以对于每次海平面上升。我们都对临海且海拔等于海平面的地区做一次 B F S BFS BFS,将所有与当前地区相邻,并且海拔小于等于当前地区的地方都永久标记一下,标识当前地区已经被淹没了。

3、对于我们 B F S BFS BFS 遍历不到的地区,我们应该再次将其加入到我们之前的 m a p map map 里面。因为,即然当前地区是我第一个遍历不到的地区,那说明在遍历它之前的地区,都是可以遍历的。可以遍历就代表被淹没了,所以不可遍历的地区就变成了临海地区了。

是要实现了上述三个步骤,那么这题就引刃而解了。接下来上代码。
p s . ps. ps. 这题真的比 D D D 简单太多了,当时 D D D 做的脑壳都昏了,做完就只剩十几分钟了,火急火燎看完这题,构思编码最后五秒成功提交还是 W A WA WA 了。嘻嘻,当时因为太急了,没有将新的临海地区加到 m a p map map 里去,所以以上三步缺一不可)

AC代码:

#include <bits/stdc++.h>
#define _1 first
#define _2 second
#define gps(x) std::fixed << std::setprecision(x)
using i64 = long long;

const int N = 1e3 + 5, M = 5e5 + 5;
const int mod = 998244353;
const i64 INF = 1e18;

int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};

int n, m, k;
int g[N][N];
bool st[N][N];
std::map<int, std::vector<std::pair<int, int>>> mp;

int bfs(int u, int v) {
    int res = 1;
    std::queue<std::pair<int, int>> pq;
    pq.push({u, v});
    st[u][v] = true;
    while (pq.size()) {
        auto now = pq.front();  pq.pop();
        
        for (int i = 0; i < 4; i ++) {
            int x = now._1 + dx[i], y = now._2 + dy[i];
            if (x < 1 || x > n || y < 1 || y > m)   continue ;
            if (g[x][y] > g[u][v]) {
                mp[g[x][y]].push_back({x, y});	// 新临海地区坐标
                continue ;
            }
            if (st[x][y])   continue ;
            res ++ ;
            st[x][y] = true ;
            
            pq.push({x, y});
        }
    }
    return res;
}

void solve() {
    std::cin >> n >> m >> k;
    
    for (int i = 1; i <= n; i ++) {
        for (int j = 1; j <= m; j ++) {
            std::cin >> g[i][j];
            if (i == 1 || i == n || j == 1 || j == m) {
                mp[g[i][j]].push_back({i, j});		// 临海地区坐标加到mp里去
            }
        }
    }
    
    int ans = n * m;	// 初始面积
    for (int i = 1; i <= k; i ++) {
        if (mp[i].size() == 0)  std::cout << ans << '\n';		// 如果一个海平面上升,并没有与之同样的海拔的地区,就说明没有淹没任何地区,没必要搜索,直接输出答案即可
        else {
            int del = 0;
            for (int j = 0; j < mp[i].size(); j ++) {
                if (st[mp[i][j]._1][mp[i][j]._2])   continue ;	// 已经遍历过的就没必要在遍历了
                del += bfs(mp[i][j]._1, mp[i][j]._2);
            }
            
            ans -= del;
            std::cout << ans << '\n';
        }
    }
    return ;
}

int main()
{
    std::ios::sync_with_stdio(false);std::cout.tie(nullptr);std::cout.tie(nullptr);
    
    int _ = 1;  // std::cin >> (_);
    while (_ --) {
        solve();
    }
    return 0;
}

D - Palindromic Number

题目大意:

给你一个 n n n,让你求:从 0 0 0 开始的,第 n n n 个回文数字。

回文数字: 363 363 363 12344321 12344321 12344321 0 0 0

数据范围: 1 ≤ n ≤ 1 0 18 1 \leq n \leq 10^{18} 1n1018

分析:

这题这数据无法暴力,想过 d p dp dp,但是也不好处理,注意到样例:当 n = 1 0 18 n = 10^{18} n=1018 时, a n s = 90000000000000000000000000000000009 ans = 90000000000000000000000000000000009 ans=90000000000000000000000000000000009 35 35 35 位数字,那就在本机预暴力一遍吧。我暴力了 1 ∼ 9 1 \sim 9 19 位数字中,各位回文数的数量,得到结果如下:
10 , 9 , 90 , 90 , 900 , 900 , 9000 , 9000 , 90000 10, 9, 90, 90, 900, 900, 9000, 9000, 90000 10,9,90,90,900,900,9000,9000,90000

剩下的位数也不好再暴力了,大胆预测一下,后面的数量将会是 90000 , 900000 , 900000 , 9000000 , . . . . 90000, 900000, 900000, 9000000, .... 90000,900000,900000,9000000,....

当这个数列的前 35 35 35 项加起来的时候,正好是大于 1 0 18 10^{18} 1018 的,则说明这个数列很大可能是正确的。

但其实不用预测也很容易算出来,当有 9 9 9 位数字时,我们可自主更改的位数是 ( 9 + 1 ) / 2 (9 + 1) / 2 (9+1)/2 ,如下所示:
00000 ∣ 0000 00000|0000 00000∣0000
因为要形成回文数,所以右边的数字只能跟着我左边的变化而变化,为了方便说明,我们称左边部分从左至右分别为万、千、百、十、个。变化条件满足如下所说:
∙ \bullet 个位变化 10 10 10 次,则十位变化 1 1 1 次。
∙ \bullet 十位变化 10 10 10 次,则百位变化 1 1 1 次。
∙ \bullet 百位变化 10 10 10 次,则千位变化 1 1 1 次。
∙ \bullet 千位变化 10 10 10 次,则万位变化 1 1 1 次。
∙ \bullet 而万位只能变化 9 9 9 次,因为再变化一次就要进位了。

b i t i bit_{i} biti i i i 位数中有 b i t i bit_{i} biti 个回文数,则 b i t 9 = 9 × 10 × 10 × 10 × 10 = 90000 bit_{9} = 9 \times 10 \times 10 \times 10 \times 10 = 90000 bit9=9×10×10×10×10=90000。如此计算可得到,当十位数时,如下划分:
00000 ∣ 00000 00000|00000 00000∣00000

同样计算出来 b i t 10 = 90000 bit_{10} = 90000 bit10=90000,则得到上述中序列的正确性,以及“我们可自主更改的位数是 ( 9 + 1 ) / 2 (9 + 1) / 2 (9+1)/2”这句话的正确性。

现在,我们只需要求出第 n n n 个回文的位数,在构造其数字即可。如何构造呢?以 9 9 9 位数为例:
00000 ∣ 0000 00000|0000 00000∣0000
我们依旧称左边部分从左至右分别为万、千、百、十、个。上述内容已经写出了每一位变化的效用,那就可以得到。
∙ \bullet 个位变化 1 1 1 次,需要用掉 1 1 1 n n n
∙ \bullet 十位变化 1 1 1 次,需要用掉 10 10 10 n n n
∙ \bullet 百位变化 1 1 1 次,需要用掉 100 100 100 n n n
∙ \bullet 千位变化 1 1 1 次,需要用掉 1000 1000 1000 n n n
∙ \bullet 千位变化 1 1 1 次,需要用掉 10000 10000 10000 n n n

我们推出第 n n n 个回文是几位数时, n n n 已经变小了,我们只需要对于剩余的 n n n 对答案做修改即可。

( p s . ps. ps. 也不知道这样有没有讲清楚,这种纯数学 + + + 思路 + + + 代码能力的题确实很麻烦,需要花点时间。)

AC代码:

#include <bits/stdc++.h>
#define _1 first
#define _2 second
#define gps(x) std::fixed << std::setprecision(x)
using i64 = long long;

const int N = 50, M = 5e5 + 5;
const int mod = 998244353;
const i64 INF = 1e18;

i64 n, m, k;
i64 rec[N], bit[N];

void solve() {
    std::cin >> n;

    int idx = 0;
    for (int i = 1; i <= 36; i ++) {
        if (n <= rec[i]) {
            idx = i;
            break ;
        }
    }
    
    n -= rec[idx - 1] + 1;
    std::vector<int> ans(idx + 5, 0);
    
    if (idx != 1)   ans[1] = ans[idx] = 1;		// 当1 == idx时,说明只有一位数,此时0也要算进去。
    
    i64 now = bit[idx] / 9;
    int l = 1, r = idx;
    while (n) {
        if (n >= now) {
            if (l != r) {		// 为了防止奇数位时,最中间那位多加了
                ans[l] ++ ;
                ans[r] ++ ;
            }
            else    ans[l] ++ ;
            n -= now;
        }
        else {
            now /= 10;
            l ++;   r -- ;
        }
    }
    
    for (int i = 1; i <= idx; i ++) {
        std::cout << ans[i];
    } std::cout << '\n';
    
    return ;
}

void __init__() {
    bit[1] = 10;
    bit[2] = 9;
    bit[3] = 90;
    for (int i = 4; i <= 36; i ++) {
        if (bit[i - 1] != bit[i - 2])    bit[i] = bit[i - 1];
        else    bit[i] = bit[i - 1] * 10;
    }
    
    for (int i = 1; i <= 36; i ++) {
        rec[i] = rec[i - 1] + bit[i];
    }
    return ;
}

int main()
{
    std::ios::sync_with_stdio(false);std::cout.tie(nullptr);std::cout.tie(nullptr);
    
    __init__();
    
    int _ = 1;  // std::cin >> (_);
    while (_ --) {
        solve();
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值