备注:题目后的难度评价是这样制定的
1.若 l u o g u luogu luogu 上有难度评价(除 暂无评价 外),则为 l u o g u luogu luogu 上的难度评价。
2.否则,作者将参考其他类似题目的难度评价与个人主观来评价其难度。
S
搜索
1.城市距离( 普 及 + / 提 高 \textcolor{green}{普及+/提高} 普及+/提高)
U p d Upd Upd: 2022.11.28 2022.11.28 2022.11.28 完成
八中OJ链接
我们还是先观察数据范围: 1 ≤ n , m ≤ 500 1 \leq n,m \leq 500 1≤n,m≤500,那么就只能用广度优先搜索了。
个人认为这道题出得不好,因为按照目前的最优解,时间复杂度应该为 O ( n 2 m 2 ) O(n^2m^2) O(n2m2) 的,应该是过不掉的,但为什么在 150 m s 150ms 150ms 内就过掉了呢?原因有二:一是数据太水,仅有一组极限数据并且 ‘#’ 符号少得可怜。二是如果每一次 b f s bfs bfs 不用 O ( n m ) O(nm) O(nm) 的时间初始化,其实真正的时间复杂度是远远小于 O ( n 2 m 2 ) O(n^2m^2) O(n2m2) 的。
我们先来想一想最暴力的做法,枚举两个城市,再用 O ( n m ) O(nm) O(nm) 求其最短距离,这样做是 O ( n 3 m 3 ) O(n^3m^3) O(n3m3) 的,绝对会原地起飞。
现在,来说说这道题的正确做法——染色法,即将每个城市用 d f s dfs dfs 染色后去求解。如果每个城市只有一个 #,那么,这个问题就是一个很裸的 B F S BFS BFS 问题。但是,现在的问题就是,要如何
做,才能够求得一片城市与另一片城市的最短距离?其实,每一次 b f s bfs bfs 最坏会遍历整个地图,并且我们只是求其最小距离,所以,我们可以将某一个城市内的所有 # 全都作为我们搜索的起点,然后计算从这些起点当中,走到另一个 # 的最短距离是多少( B F S BFS BFS),因为我们只需要找到最小距离,并不需要知道究竟是哪个城市到了另外哪个城市,因此,我们在跑 B F S BFS BFS 之前,找到当前我们找的这个城市有哪些 #,将同一个城市内的所有#都 p u s h push push 进我们的队列当中,然后,再跑一个 B F S BFS BFS,找到这个城市能走到的离它最近的城市的距离,最后取最小的那个答案即可。
A C AC AC C o d e : Code: Code:
#include <bits/stdc++.h>
using namespace std;
const int N = 5e2 + 5, INF = 0x3f3f3f3f;
const int zx[4] = {
-1, 0, 1, 0}, zy[4] = {
0, 1, 0, -1};
int n, m, ans = INF;
char c[N][N];
bool vis[N][N];
struct node {
int x, y, step;
};
queue<node> q;
bool in(int x, int y) {
return x >= 1 && x <= n && y >= 1 && y <= m;
}
void dfs(int x, int y) {
c[x][y] = 'A';
q.push((node){
x, y, 0});
for(int i = 0;i < 4;i++) {
int dx = x + zx[i], dy = y + zy[i];
if(in(dx, dy) && c[dx][dy] == '#') dfs(dx, dy);
}
}
void bfs() {
for (int i = 1;i <= n;i++)
for (int j = 1;j <= m;j++)
vis[i][j] = 0;
while(!q.empty()) {
node p = q.front();
q.pop();
if(c[p.x][p.y] == '#') {
while(!q.empty()) q.pop();
ans = min(ans, p.step);
return ;
}
for(int i = 0;i < 4; ++i) {
int dx = p.x + zx[i], dy = p.y + zy[i];
if(in(dx, dy) && !vis[dx][dy] && c[dx][dy] != 'A') {
vis[dx][dy] = 1;
q.push((node){
dx, dy, p.step + 1});
}
}
}
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 1;i <= n;i++) scanf("%s", c[i] + 1);
for(int i = 1;i <= n;i++)
for(int j = 1;j <= m;j++)
if(c[i][j] == '#') {
dfs(i, j);
bfs();
}
printf("%d", ans);
return 0;
}
数位DP
1.手机号码(CQOI2016,, 省 选 / N O I − \textcolor{purple}{省选/NOI-} 省选/NOI−)
U p d Upd Upd: 2022.11.26 2022.11.26 2022.11.26 完成
洛谷链接 八中OJ链接
看到数据范围 1 0 10 ≤ L ≤ R ≤ 1 0 11 10^{10} \leq L \leq R \leq 10^{11} 1010≤L≤R≤1011 并且是统计 [ L , R ] [L,R] [L,R] 内有满足条件的号码数量,很容易想到数位 D P DP DP。
其实,数位 D P DP DP 也是一个很模板化的东西。对其总结如下:
dfs(数的最后若干位,各种限制条件,当前第几位)
if 最后一位
return 各种限制条件下的返回值
局部变量 ct = 当前位的数字
局部变量 sum = 0;
for i = 0 to ct - 1
sum += 当前位取i时一定无无限制的合法状态数
sum += 当前位取i时满足当前限制的合法状态数
根据ct更新限制条件 不再满足则 return sum
return sum + dfs(当前位后的若干位,更新后的限制条件,下一位)
slv(当前数)
if (只有一位) return 对应的贡献
局部变量 ct;
for ct = 可能最高位 to 1
if 当前位有数字 break
局部变量 nw = 当前位数字
局部变量 sum = 0
for i = 1 to nw - 1
sum += 当前位取i后合法情况任意取的贡献
for i = 1 to ct-1
for j = 1 to 9
sum += 第i位取j后合法情况任意取的贡献
sum += dfs(去掉第一位后的若干位,限制条件,第二位)
return sum
main
预处理当前位取i的各种条件各种限制的贡献
读入 L R
--L
输出 slv(R)-slv(L)
return 0
而在这道题中,我们就可以很容易的套模板,定义六维的 D P DP DP 去记忆化 d f s dfs dfs,即 d p [ 第 i 位 ] [ 填 j ] [ 目 前 连 续 L ] [ 是 否 有 4 ] [ 是 否 有 8 ] [ 是 否 有 至 少 3 位 连 续 数 字 ] dp[第i位][填j][目前连续L][是否有4][是否有8][是否有至少3位连续数字] dp[第i位][填j][目前连续L][是否有4][是否有8][是否有至少3位连续数字]。
然后按照板子按部就班的做便能 A C AC AC.
A C AC AC C