Luogu P8250 交友问题 题解 [ 蓝 ] [ 根号分治 ] [ Bitset ] [ 复杂度均摊 ]
交友问题:很有趣的一道根号分治。
首先对于这种一看就很能按度数暴力枚举的题,想到“利用点的度数进行根号分治”的 trick 是容易的。于是可以先求出每个点的度数,设阈值为 \(B\),有两种做法:
Sol.1 Bitset 做法
因为本题不是 recall,所以开 \(2\times 10^5\) 个 Bitset 显然是不行的。因此我们只对超过阈值的开 Bitset,不超过阈值的就暴力枚举。容易发现一共最多需要开 \(\min(n, \dfrac{2m}{B})\) 个 Bitset。
考虑对两点超过 / 未超过阈值进行分类讨论:
- \(deg_u, deg_v \ge B\)
- 此时可以直接利用 \(u,v\) 预处理的 Bitset 求答案,将 \(v\) 的 Bitset 取反后和 \(u\) 的 Bitset 按位与,然后利用
count函数即可。时间复杂度 \(O(\dfrac{n}{w})\)。
- 此时可以直接利用 \(u,v\) 预处理的 Bitset 求答案,将 \(v\) 的 Bitset 取反后和 \(u\) 的 Bitset 按位与,然后利用
- \(deg_u < B, deg_v \ge B\)
- 显然可以直接暴力枚举 \(u\) 的邻域,判断邻域是否为 \(v\) 的邻域即可。时间复杂度 \(O(B)\)。
- \(deg_v < B, deg_u \ge B\)
- 显然也是枚举 \(v\) 的邻域,判断一下,用 \(u\) 的度数减掉这个值即可。时间复杂度 \(O(B)\)。
- \(deg_u, deg_v < B\)
- 暴力枚举 \(u,v\) 的邻域,判个交即可。时间复杂度 \(O(B)\)。
复杂度是 \(O(m\min(n, \dfrac{2m}{B})+q(\dfrac{n}{w} + B))\) 的。平衡一下发现 \(B\) 取 \(\sqrt{2m}\) 是比较优秀的。总体时间复杂度 \(O((m+q)\sqrt{m} + \dfrac{qn}{w})\)。
#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi = pair<int, int>;
const int N = 200005, M = 700005, B = 1200;
int n, m, q, deg[N], id[N], cnt;
int h[N], idx;
struct Edge{
int v, ne;
}e[2 * M];
void add(int u, int v)
{
e[++idx] = {v, h[u]};
h[u] = idx;
}
bitset<N> vis[B + 10], tmp;
int main()
{
//freopen("sample.in", "r", stdin);
//freopen("sample.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m >> q;
for(int i = 1; i <= m; i++)
{
int u, v;
cin >> u >> v;
add(u, v);
add(v, u);
deg[u]++; deg[v]++;
}
for(int i = 1; i <= n; i++)
{
if(deg[i] >= B)
{
id[i] = ++cnt;
for(int j = h[i]; j ; j = e[j].ne)
{
int v = e[j].v;
vis[cnt][v] = 1;
}
vis[cnt][i] = 1;
}
}
while(q--)
{
int x, y;
cin >> x >> y;
if(id[x] && id[y])
{
int ans = ((~vis[id[y]]) & (vis[id[x]])).count();
if(vis[id[y]][x] == 0) ans--;
cout << ans << '\n';
}
else if(id[x])
{
int ans = 0;
for(int i = h[y]; i ; i = e[i].ne)
{
int v = e[i].v;
ans += vis[id[x]][v];
}
cout << deg[x] - ans << '\n';
}
else if(id[y])
{
int ans = 0;
for(int i = h[x]; i ; i = e[i].ne)
{
int v = e[i].v;
ans += (vis[id[y]][v] ^ 1);
}
cout << ans << '\n';
}
else
{
int ans = 0;
tmp[y] = 1;
for(int i = h[y]; i ; i = e[i].ne)
{
int v = e[i].v;
tmp[v] = 1;
}
for(int i = h[x]; i ; i = e[i].ne)
{
int v = e[i].v;
ans += (tmp[v] ^ 1);
}
cout << ans << '\n';
for(int i = h[y]; i ; i = e[i].ne)
{
int v = e[i].v;
tmp[v] = 0;
}
tmp[y] = 0;
}
}
return 0;
}
Sol.2 边数均摊做法
口胡一下,没有写代码。
注意到 \(deg\ge B\) 的点最多有 \(\dfrac{2m}{B}\) 个,所以对这些度数大的点进行预处理。具体地,我们可以遍历整张图,固定下这个点作为 \(u,v\) 后枚举 \(v,u\),然后暴力计算答案。因为每条边最多遍历两次,所以均摊复杂度是 \(O(m)\) 的。该部分总体复杂度 \(O(\dfrac{m^2}{B})\)。
依然是进行分类讨论:
- \(deg_u, deg_v < B\)
- 按上文做法直接暴力枚举即可。时间复杂度 \(O(B)\)。
- 否则 \(u,v\) 之中必有一个点的度数大于等于阈值,直接根据预处理的结果输出即可。
复杂度是 \(O(\dfrac{m^2}{B} + qB)\) 的,平衡一下复杂度,发现 \(B = \sqrt{\dfrac{m^2}{q}}\) 是比较优秀的。总体复杂度是 \(O(\sqrt{m^2q})\)。

浙公网安备 33010602011771号