题解:uoj695 【候选队互测2022】毛估估就行
题意:给出一个无向无权图,\(q\) 次询问两点距离,但是假设真实距离为 \(d\),输出 \([d-1,d+1]\) 都视为正确。\(n\le 8000,q\le 10^6,m\le n^2\)。
做法:
正常的最短路肯定是没法做,做出来就得图灵奖了。注意到输出 \(|ans-d|\le 1\) 都可以,我们考虑去找一些关键点出来对整个图跑 bfs,假设 \((x,y)\) 的最短路径经过关键点 \(u\) 或者 \(u\) 的邻域,那么就会有 \(dis(x,u)+dis(y,u)-2\le dis(x,y)\le dis(x,u)+dis(y,u)\)。直接输出 \(dis(x,u)+dis(y,u)-1\) 就可以保证是对的。询问时不太方便去找 \(u\),但是我们发现对所有的关键点取 \(\min\) 肯定是没问题的,不相邻的肯定差的更多,这样输出就可以了。
但是这样显然不够快,还是 \(O(n)\) 个关键点和 \(O(n^2)\) 的 bfs 合起来是 \(n^3\) 的。我们考虑,当我们拉出来一个关键点后,我们就直接把他和他的邻域全删了,不用在后面的 bfs 中跑。可以证明是 \(O(n^2)\) 的,接下来我们说明一下这个事情。
假设我们做到第 \(i\) 个关键点的时候剩下 \(m_i\) 条边,那么这个关键点目前的度数一定 \(\ge \frac{2m_i}{n}\),也就是我们会同时删除的点的个数。那么我们会有:
第二个等号的原因是因为所有的点和其邻域的点只会被删一次。
那么我们总共 bfs 的复杂度就是 \(O(\sum m_i) = n^2\)。
这样我们可以做到预处理 \(O(n^2)\),询问 \(O(nq)\)。
发现这样预处理和询问非常不协调,考虑平衡一下,我们不拉出来 \(n\) 个关键点,而是选度数最大的前 \(B\) 个。对于剩下没删掉的点就直接暴力跑出两点距离,输出就直接对暴力的和有关键点的取 \(\min\) 即可,询问复杂度 \(O(qB)\)。
考虑预处理,首先前面的 bfs 部分肯定还是不大于 \(O(n^2)\) 的复杂度,不用管,主要是后面的复杂度。我们注意到,此时每个点的度数最多为 \(\frac{n}B\),因为如果有多的,那么前面每轮至少删掉 \(\frac{n}B\) 个就删空了,这样总边数就是 \(O(\frac{n^2}{B})\) 的,暴力 bfs 复杂度为 \(O(\frac{n^3}{B})\)。
取 \(B = \sqrt{\frac{n^3} q}\) 即可,复杂度 \(O(n^{1.5}q^{0.5})\)。
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 8005;
int deg[maxn], n, q;
vector<int> e[maxn];
int get_val(char c) {
return (isdigit(c) ? c - '0' : c - 'A' + 10);
}
void del(int x) {
deg[x] = -1;
for (vector<int>::iterator it = e[x].begin(); it != e[x].end(); it++)
if(deg[*it] != -1)
deg[*it]--;
}
int dis[maxn][maxn];
void bfs(int s) {
queue<int> q;
dis[s][s] = 0;
q.push(s);
while(!q.empty()) {
int u = q.front(); q.pop();
for (vector<int>::iterator it = e[u].begin(); it != e[u].end(); it++) {
int v = *it;
if(deg[v] != -1 && dis[s][v] > dis[s][u] + 1) {
dis[s][v] = dis[s][u] + 1;
q.push(v);
}
}
}
}
int p[maxn];
int main() {
ios::sync_with_stdio(false);
cin >> n >> q;
for (int i = 2; i <= n; i++) {
string s; cin >> s;
for (int j = 1; j < i; j++) {
int t = get_val(s[(j - 1) / 4]);
if((t >> ((j - 1) % 4)) & 1)
e[i].push_back(j), e[j].push_back(i), deg[i]++, deg[j]++;
}
}
memset(dis, 0x3f, sizeof(dis));
int B = 100;
for (int i = 1; i <= B; i++) {
int pos = 0;
for (int j = 1; j <= n; j++)
if(deg[j] > deg[pos])
pos = j;
if(pos == 0)
break;
p[i] = pos;
bfs(pos);
del(pos);
for (vector<int>::iterator it = e[pos].begin(); it != e[pos].end(); it++)
if(deg[*it] != -1)
del(*it);
}
for (int i = 1; i <= n; i++) {
if(deg[i] == -1)
continue;
vector<int> res;
for (int j = 0; j < e[i].size(); j++)
if(deg[e[i][j]] != -1)
res.push_back(e[i][j]);
swap(res, e[i]);
}
for (int i = 1; i <= n; i++) {
if(deg[i] != -1)
bfs(i);
}
while(q--) {
int x, y; cin >> x >> y;
int ans = dis[x][y];
for (int i = 1; i <= B; i++)
if(p[i])
ans = min(ans, dis[p[i]][x] + dis[p[i]][y] - 1);
cout << ans << '\n';
}
return 0;
}

浙公网安备 33010602011771号