T1 [JZOJ1235] 洪水
题目描述
一天, 一个画家在森林里写生,突然爆发了山洪,他需要尽快返回住所中,那里是安全的。
森林的地图由R行C列组成,空白区域用点“.”表示,洪水的区域用“*”表示,而岩石用“X”表示,另画家的住所用“D”表示,画家用“S”表示。
有以下几点需要说明:
1、 每一分钟画家能向四个方向移动一格(上、下、左、右)
2、 每一分钟洪水能蔓延到四个方向的相邻格子(空白区域)
3、 洪水和画家都不能通过岩石区域
4、 画家不能通过洪水区域(同时也不行,即画家不能移到某个格子,该格子在画家达到的同时被洪水蔓延到了,这也是不允许的)
5、 洪水蔓不到画家的住所
给你森林的地图,编写程序输出最少需要花费多长时间才能从开始的位置赶回家中(如果画家不可能安全回家则输出“KAKTUS”)。
数据范围
$2 leq R,C leq 50$
分析
听说今天要加大难度,结果开局又是一道水题
先 $BFS$ 预处理出每个区域最早被洪水蔓延的时间,再 $BFS$ 让画家把能走的路走一遍,如果能到达就输出最小步数,否则输出 $KAKTUS$
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> using namespace std; #define ll long long #define inf 0x3f3f3f3f #define N 55 int n, m, x0, y0, x1, y1; int t[N][N], d[N][N], vis[N][N]; int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0}; char g[N][N]; struct Point { int x, y; } p; bool check(int x, int y) { if (x < 1 || x > n) return false; if (y < 1 || y > m) return false; if (g[x][y] == 'X') return false; return true; } void bfs(int x, int y) { queue<Point> q; t[x][y] = 0; p.x = x; p.y = y; q.push(p); while (!q.empty()) { Point temp = q.front(); q.pop(); for (int i = 0; i < 4; i++) { int dx = temp.x + dir[i][0]; int dy = temp.y + dir[i][1]; if (check(dx, dy) && g[dx][dy] != 'D') if (t[temp.x][temp.y] + 1 < t[dx][dy]) { t[dx][dy] = t[temp.x][temp.y] + 1; p.x = dx; p.y = dy; q.push(p); } } } } void BFS() { queue<Point> q; memset(d, 0x3f, sizeof d); d[x0][y0] = 0; p.x = x0; p.y = y0; q.push(p); while (!q.empty()) { Point temp = q.front(); q.pop(); for (int i = 0; i < 4; i++) { int dx = temp.x + dir[i][0]; int dy = temp.y + dir[i][1]; if (check(dx, dy) && !vis[dx][dy]) if (d[temp.x][temp.y] + 1 < t[dx][dy]) { d[dx][dy] = d[temp.x][temp.y] + 1; if (g[dx][dy] == 'D') return; p.x = dx; p.y = dy; q.push(p); vis[dx][dy] = 1; } } } } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) { scanf(" %c", &g[i][j]); if (g[i][j] == 'S') x0 = i, y0 = j; if (g[i][j] == 'D') x1 = i, y1 = j; } memset(t, 0x3f, sizeof t); for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) if (g[i][j] == '*') bfs(i, j); BFS(); if (d[x1][y1] == inf) printf("KAKTUS "); else printf("%d ", d[x1][y1]); return 0; }
T2 [JZOJ1236] 邦德I
题目描述
每个人都知道詹姆斯邦德,著名的007,但很少有人知道很多任务都不是他亲自完成的,而是由他的堂弟们吉米邦德完成(他有很多堂弟),詹姆斯已经厌倦了把一个个任务分配给一个个吉米,他向你求助。
每个月,詹姆斯都会收到一些任务,根据他以前执行任务的经验,他计算出了每个吉米完成每个任务的成功率,要求每个任务必须分配给不同的人去完成,每个人只能完成一个任务。
请你编写程序找到一个分配方案使得所有任务都成功完成的最大概率。
数据范围
$1 leq N leq 20$
分析
显然状压 $DP$
用二进制表示每种状态下已经分配了任务的人员情况,然后枚举完成第 $k$ 项任务的人($k$ 为当前状态已完成的任务),在所有前继状态中取最大值
最后所有人分配到任务的状态得到的最大概率即为答案
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> using namespace std; #define ll long long #define inf 0x3f3f3f3f #define N 21 int n, m, sum[1 << N]; double g[N][N], f[1 << N]; int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) { scanf("%d", &m); g[i][j] = m / 100.0; } f[0] = 1; sum[0] = 0; for (int s = 1; s < (1 << n); s++) for (int i = 1; i <= n; i++) if (s & (1 << i - 1)) { int last = s ^ (1 << i - 1); if (!sum[s]) sum[s] = sum[last] + 1; f[s] = max(f[s], f[last] * g[i][sum[s]]); } printf("%.6lf ", f[(1 << n) - 1] * 100.0); return 0; }
T3 [JZOJ1237] 餐桌
题目描述
你家刚买了一套新房,想邀请朋友回来庆祝,所以需要一个很大的举行餐桌,餐桌能容纳的人数等于餐桌的周长,你想买一个能容纳最多人的餐桌,餐桌的边必须跟房间的边平行。
输入中“.”表示空白区域,“X”表示有障碍物,餐桌所占区域必须是空白的。
给你的房间的设计,计算最多能邀请的客人数。
数据范围
$1 leq R,C leq 2 imes 10^3$
分析
题目样例让大家看得很懵逼,原来客人数是桌子边长减去一个主人
我们可以预处理每个点向上走最多有几块空地($f$),然后再枚举每个点作为矩形的左下角,向右扩展形成新的矩形来不断更新答案
这是一个 $O(n^3)$ 的做法,但足以水过数据
为了优化算法,在每一行上可以用单调栈来维护 $f$ 递增,每次加入新元素时,先计算出栈顶元素对应的答案,然后再弹出栈顶元素维护栈内单调
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> using namespace std; #define ll long long #define inf 0x3f3f3f3f #define N 2005 int n, m, ans; int f[N][N], s[N], l[N]; char x; int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) { scanf(" %c", &x); if (x == '.') f[i][j] = f[i - 1][j] + 1; } for (int i = 1; i <= n; i++) { int top = 0, len; for (int j = 1; j <= m; j++) { len = 0; while (s[top] > f[i][j] && top) { len += l[top]; ans = max(ans, s[top] + len); top--; } if (f[i][j]) { s[++top] = f[i][j]; l[top] = len + 1; } } len = 0; while (top) { len += l[top]; ans = max(ans, s[top] + len); top--; } } printf("%d ", ans * 2 - 1); return 0; }
T4 [JZOJ1238] 自行车比赛
题目描述
自行车赛在一个很大的地方举行,有N个镇,用1到N编号,镇与镇之间有M条单行道相连,起点设在镇1,终点设在镇2。
两条路线只要不使用完全相同的道路就被认为是不同的,问从起点到终点一共有多少种不同的路线(如果有无穷多的路线,输出“inf”)。
数据范围
$1 leq N leq 10^4$,$1 leq M leq 10^5$
分析
考场上想都没想就觉得图里有环就输出 $inf$,结果挂了 $50 \, tps$
实际上只有 $1$ 能经过环再到 $2$ 时才会有无限条路线
然而实践证明,数据中并没有 $inf$ 的点
听完大佬们 $Tarjan$、$DAG$、拓扑排序、莫队(雾)的讲解,感觉很强
我发现我大概是全场最无脑的做法,而且跑得还挺快(跻身最优解第一面)
我们分别在两个图中存入正向边和反向边
先在正向图上 $DFS$ 标记从 $1$ 点出发可以到达的所有点,再在反向图上从 $2$ 开始 $DFS$ 查找是否存在环
如果在图中找到了环,且环上的点已被标记,那么就说明可以从 $1$ 经过环再到达 $2$ 了
如果不存在上述情况,则在正向图上 $DFS$(记忆化)得到每个点到 $2$ 的路线总数
想不到吧,三个 $DFS$,竟然就 $AC$ 了
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> using namespace std; #define ll long long #define inf 0x3f3f3f3f #define N 10005 #define M 100005 int n, m, u, v, tot, tot1, ans, cir, ok, p = 1000000000; int to[M], nxt[M], head[N], vis[N], sum[N]; int to1[M], nxt1[M], head1[N], vis1[N]; void add(int uu, int vv) { to[++tot] = vv; nxt[tot] = head[uu]; head[uu] = tot; to1[++tot1] = uu; nxt1[tot1] = head1[vv]; head1[vv] = tot1; } void dfs1(int x) { if (x == 2 || vis[x]) return; vis[x] = 1; for (int i = head[x]; i; i = nxt[i]) dfs1(to[i]); } void dfs2(int x) { if (x == 1) return; if (vis1[x] == 1) { if (vis[x]) cir = 1; return; } vis1[x] = 1; for (int i = head1[x]; i; i = nxt1[i]) { if (vis1[to1[i]] != 2) dfs2(to1[i]); if (cir) return; } vis1[x] = 2; } int dfs(int x) { if (x == 2) return 1; if (vis[x]) return sum[x]; vis[x] = 1; for (int i = head[x]; i; i = nxt[i]) { sum[x] += dfs(to[i]); if (sum[x] >= p) sum[x] %= p, ok = 1; } return sum[x]; } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= m; i++) { scanf("%d%d", &u, &v); add(u, v); } dfs1(1); dfs2(2); if (cir) {printf("inf "); return 0;} memset(vis, 0, sizeof vis); ans = dfs(1); if (ok) { int num = 0; p /= 10; while (!(ans / p) && p) p /= 10, num++; while (num--) printf("0"); } printf("%d ", ans); return 0; }