朋友
题目背景:
10.31 NOIP模拟T2 bzoj2143
分析:线段树优化建图 or 最短路 + 等效建图
考场上一眼n棵线段树优化建图,然后尝试感觉了一下,发现如果给5秒的话,复杂度好像很稳啊,然而当时忽略了空间只有128M这个严肃的事实(事实证明,空间128M标算都搞不过去······
讲题,先来讲我的做法,考虑对于每一个点而言,它在每一行中可以跳到的点应该是一段连续的区间,那么如果我们对于每一行建一颗线段树,那么每一行最多对应了log n个结点,也就是说,对于每一个点来说,有nlogn条边,总的边数就应该是n3logn因为数据才200,logn相当于就是常数了,然后总的点数在n2级别,跑dijkstra的复杂度就是(n3logn) * log(n3logn)理论上应该是可以过去的,实践也证明是可以的最慢的点大概也就只有2.5s左右,然而·······本来这个题有一个G的空间,教练卡成了128M,我也是······所以就GG成50啦······然而bzoj上跑的飞快······具体实现也比较简单,对于每一个点向n棵线段树对应的可行区间连边,然后在线段树中从上层向下层连边权为0的边,表示如果可以到总的区间,则一定可以到对应的子区间,然后对于叶子节点,向其对应的起始点连边,边权为0,表示,如果可以到达这个点,则一定可以从这个点出发。然后跑3次最短路即可。(有同学将左边的出发点也建了树,事实证明没有那个必要啊······)
Source:
/*
created by scarlyw
*/
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <string>
#include <cstring>
#include <cctype>
#include <vector>
#include <queue>
#include <set>
#include <ctime>
const int MAXN = 200 + 10;
const long long INF = 1000000000000000000;
int n, m, ind, pos, times;
int id[MAXN][MAXN], to[MAXN][MAXN << 2];
int a[MAXN][MAXN], b[MAXN][MAXN], x[4], y[4];
struct node {
int to;
long long w;
node(int to = 0, long long w = 0) : to(to), w(w) {}
inline bool operator < (const node &a) const {
return w > a.w;
}
} ;
std::vector<node> edge[MAXN * MAXN * 3];
inline void add_edge(int x, int y, long long w) {
++times;
edge[x].push_back(node(y, w));
}
inline void build(int k, int l, int r, int *tree) {
tree[k] = ++ind;
if (l == r) return add_edge(tree[k], id[pos][l], 0);
int mid = l + r >> 1;
build(k << 1, l, mid, tree), build(k << 1 | 1, mid + 1, r, tree);
add_edge(tree[k], tree[k << 1], 0), add_edge(tree[k], tree[k << 1 | 1], 0);
}
inline void add_edge(int cur, int w, int k, int l, int r, int ql, int qr,
int *tree) {
if (ql <= l && r <= qr) return add_edge(cur, tree[k], w);
int mid = l + r >> 1;
if (ql <= mid) add_edge(cur, w, k << 1, l, mid, ql, qr, tree);
if (qr > mid) add_edge(cur, w, k << 1 | 1, mid + 1, r, ql, qr, tree);
}
inline int get_id(int k, int l, int r, int pos, int *tree) {
if (l == r) return tree[k];
int mid = l + r >> 1;
if (pos <= mid) return get_id(k << 1, l, mid, pos, tree);
else return get_id(k << 1 | 1, mid + 1, r, pos, tree);
}
inline void read_in() {
scanf("%d%d", &n, &m), ind = n * m;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
scanf("%d", &a[i][j]);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
scanf("%d", &b[i][j]);
}
inline void build_graph() {
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
id[i][j] = (i - 1) * m + j;
for (int i = 1; i <= n; ++i) pos = i, build(1, 1, m, to[i]);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j) {
int up = std::max(i - a[i][j], 1), down = std::min(i + a[i][j], n);
for (int k = up; k <= down; ++k) {
int l = std::max(j - (a[i][j] - abs(i - k)), 1);
int r = std::min(j + (a[i][j] - abs(i - k)), m);
add_edge(id[i][j], b[i][j], 1, 1, m, l, r, to[k]);
}
}
for (int i = 1; i <= 3; ++i) scanf("%d%d", &x[i], &y[i]);
}
long long d[3];
inline void dijkstra(int s, int t1, int t2) {
static long long dis[MAXN * MAXN * 4];
static bool vis[MAXN * MAXN * 4], f1, f2;
for (int i = 1; i <= ind; ++i) dis[i] = INF, vis[i] = false;
std::priority_queue<node> q;
dis[s] = 0, q.push(node(s, dis[s])), f1 = f2 = false;
while (!q.empty()) {
while (!q.empty() && vis[q.top().to]) q.pop();
if (q.empty()) break ;
int cur = q.top().to;
(cur == t1) ? (f1 = true) : 0, (cur == t2) ? (f2 = true) : 0;
if (f1 && f2) break ;
vis[cur] = true;
for (int p = 0; p < edge[cur].size(); ++p) {
node *e = &edge[cur][p];
if (!vis[e->to] && dis[e->to] > dis[cur] + e->w)
dis[e->to] = dis[cur] + e->w, q.push(node(e->to, dis[e->to]));
}
}
d[1] = dis[t1], d[2] = dis[t2];
}
inline void solve() {
long long ans = INF * 5, dis[4][4];
char c;
int id1 = get_id(1, 1, m, y[1], to[x[1]]);
int id2 = get_id(1, 1, m, y[2], to[x[2]]);
int id3 = get_id(1, 1, m, y[3], to[x[3]]);
dijkstra(id[x[1]][y[1]], id2, id3), dis[1][2] = d[1], dis[1][3] = d[2];
dijkstra(id[x[2]][y[2]], id1, id3), dis[2][1] = d[1], dis[2][3] = d[2];
dijkstra(id[x[3]][y[3]], id1, id2), dis[3][1] = d[1], dis[3][2] = d[2];
if (x[1] == x[2] && y[1] == y[2]) dis[1][2] = dis[2][1] = 0;
if (x[1] == x[3] && y[1] == y[3]) dis[1][3] = dis[3][1] = 0;
if (x[2] == x[3] && y[2] == y[3]) dis[2][3] = dis[3][2] = 0;
(dis[2][1] + dis[3][1] < ans) ? (c = 'X', ans = dis[2][1] + dis[3][1]) : 0;
(dis[1][2] + dis[3][2] < ans) ? (c = 'Y', ans = dis[1][2] + dis[3][2]) : 0;
(dis[1][3] + dis[2][3] < ans) ? (c = 'Z', ans = dis[1][3] + dis[2][3]) : 0;
std::cout << c << '\n' << ans;
}
int main() {
// freopen("friend.in", "r", stdin);
// freopen("friend.out", "w", stdout);
read_in();
build_graph();
solve();
return 0;
}
下面是标算做法,我们在这里引入一个层级的定义,对于每一个点,我们将其拆成S个点,S为最大的弹射距离(显然S <= n + m - 2),然后对于一个点point(x, y, h)表示,当前点的坐标为(x, y),是级别h的点,我们将它向相邻的级别比它小一的点以及自己的下一级别,连边权为0的边,即:
point(x, y, h) à point(x, y, h - 1)
point(x, y, h) à point(x - 1, y, h - 1)
point(x, y, h) à point(x, y - 1, h - 1)
point(x, y, h) à point(x + 1, y, h - 1)
point(x, y, h) à point(x, y + 1, h - 1)
我们可以将这个级别h理解为它还能完全不需要花费的走多少步,对于一个点point(x, y, h)它就可以0花费走到左右和它距离不超过h的点,然后对于每一个地面节点,即h为0的节点point(x, y, 0),我们可以利用弹射机将它放到一个高度为a[x][y]的point(x, y, a[i][j])上,即
point(x, y, 0) à point(x, y, a[i][j])边权为b[i][j],表示通过话费b[i][j],使得你可以从当前节点向周围走最多a[i][j]步,这样就大大减少了原题边数到n3级别然后这样就将问题转化成了,求三个地面节点两两到达的最短路径,直接3遍dijkstra即可,注意到每个点最多只有5条边,点的数量和边的数量是同阶的,都是n3,所以dijkstra的复杂度就是n3log(n3)······················
然而上面这个复杂度是在扯淡,我们来考虑一下,若n = m,那么总点数为n3 * 2,总边数为n3 * 10,然后我们回去看看我之前的线段树,是不是n3log n啊,但是,考虑线段树的log n不是每一次都有满满的log n个区间的(也不可能有),并且,除非所有的a[i][j]都能覆盖整个图,否则有一个n也是不满的,再者,退一万步讲,这道题的n是不是200啊,你确定log 200 > 10 !!!!所以说这个标算复杂度完全相当于加了一个log,还不止啊,完全没有线段树优秀啊,事实证明时间上也比线段树多了两倍左右啊······一点都不优秀······(虽然思路的确妙妙)而且你算算空间复杂度,200 * 200 * 400个点,你开一个dis数组,要用long long你就已经快256M了吧·······
Source:
/*
created by scarlyw
*/
#include <cstdio>
#include <string>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cmath>
#include <cctype>
#include <vector>
#include <set>
#include <queue>
const int MAXN = 200 + 10;
const int MAXM = 16000000 + 10;
const long long INF = 1000000000000000000;
int n, m, ind;
int a[MAXN][MAXN];
long long b[MAXN][MAXN];
int id[MAXN][MAXN][MAXN << 1 | 1];
int x[4], y[4], max;
struct point {
int x, y, h;
point(int x = 0, int y = 0, int h = 0) : x(x), y(y), h(h) {}
inline bool operator == (const point &a) const {
return (x == a.x) && (y == a.y) && (h == a.h);
}
} ;
struct node {
point to;
long long w;
node() {}
node(point to, long long w) : to(to), w(w) {}
inline bool operator < (const node &a) const {
return w > a.w;
}
} ;
inline void read_in() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
scanf("%d", &a[i][j]), max = std::max(max, a[i][j]);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
scanf("%d", &b[i][j]);
for (int i = 1; i <= 3; ++i) scanf("%d%d", &x[i], &y[i]);
max = std::min(max, n + m - 2);
}
inline void build_graph() {
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
for (int s = 0; s <= max; ++s)
id[i][j][s] = ++ind;
}
inline int get_id(point x) {
return id[x.x][x.y][x.h];
}
long long dis[MAXM];
inline void dijkstra(point s, point t1, point t2) {
static bool vis[MAXM], f1, f2;
for (int i = 1; i <= ind; ++i) dis[i] = INF, vis[i] = false;
std::priority_queue<node> q;
dis[get_id(s)] = 0, q.push(node(s, dis[get_id(s)])), f1 = f2 = false;
while (!q.empty()) {
while (!q.empty() && vis[get_id(q.top().to)]) q.pop();
if (q.empty()) break ;
point cur = q.top().to;
(cur == t1 ? f1 = true : 0), (cur == t2 ? f2 = true : 0);
if (f1 && f2) break ;
int x = cur.x, y = cur.y, h = cur.h, c_id = id[x][y][h], v;
vis[c_id] = true, q.pop();
if (h != 0) {
if (dis[v = id[x][y][h - 1]] > dis[c_id])
dis[v] = dis[c_id], q.push(node(point(x, y, h - 1), dis[v]));
if (x != 1 && dis[v = id[x - 1][y][h - 1]] > dis[c_id])
dis[v] = dis[c_id], q.push(node(point(x - 1, y, h - 1), dis[v]));
if (y != 1 && dis[v = id[x][y - 1][h - 1]] > dis[c_id])
dis[v] = dis[c_id], q.push(node(point(x, y - 1, h - 1), dis[v]));
if (x != n && dis[v = id[x + 1][y][h - 1]] > dis[c_id])
dis[v] = dis[c_id], q.push(node(point(x + 1, y, h - 1), dis[v]));
if (y != m && dis[v = id[x][y + 1][h - 1]] > dis[c_id])
dis[v] = dis[c_id], q.push(node(point(x, y + 1, h - 1), dis[v]));
} else {
if (dis[v = id[x][y][std::min(a[x][y], max)]] > dis[c_id] + b[x][y])
dis[v] = dis[c_id] + b[x][y],
q.push(node(point(x, y, std::min(a[x][y], max)), b[x][y]));
}
}
}
inline void solve() {
static long long d[4][4], ans = INF;
char c;
point t1 = point(x[1], y[1], 0);
point t2 = point(x[2], y[2], 0);
point t3 = point(x[3], y[3], 0);
dijkstra(t1, t2, t3), d[1][2] = dis[get_id(t2)], d[1][3] = dis[get_id(t3)];
dijkstra(t2, t1, t3), d[2][1] = dis[get_id(t1)], d[2][3] = dis[get_id(t3)];
dijkstra(t3, t1, t2), d[3][1] = dis[get_id(t1)], d[3][2] = dis[get_id(t2)];
if (d[2][1] + d[3][1] < ans) c = 'X', ans = d[2][1] + d[3][1];
if (d[1][2] + d[3][2] < ans) c = 'Y', ans = d[1][2] + d[3][2];
if (d[1][3] + d[2][3] < ans) c = 'Z', ans = d[1][3] + d[2][3];
(ans == INF) ? std::cout << "NO" : std::cout << c << '\n' << ans;
}
int main() {
read_in();
build_graph();
solve();
return 0;
}