星星
题目背景:
分析:玄学 + 复杂度分析
表示在台下分分钟用700个点200000条边的近似完全图把自己卡爆了······然后出题人竟然良心的700个点只开了100000,真是谢谢了······
先来说下本宝宝的方法,直接枚举每一条边,然后看这条边两边的点的边集的交集,这个交集怎么做呢,我一开始的想法是,直接将边集在最开始的时候排序(用vector存的边),然后对于小一点的边集中的每一个点,在大边集中查询是否存在,这样的复杂度是min_n * log(max_n)然后这种情况在稀疏图的时候快的飞起,但是如果是一个完全图,那就T飞飞了······然后我们再来想完全图,因为我们的两个边集是有序的,那么显然的下一个点在大边集中的位置一定在上一个之后,那么我么可以直接单调处理,这样做的复杂度是min_n + max_n,但是显然,如果有一个很大的菊花图,这样做就直接n2了······显然又一次GG,然后想了这两种情况之后我突然发现了,前一种方式在两点度数差较大的时候比较管用,后一种在两点度数差小的时候适用,为什么不直接判断一下,感觉很稳的样子啊······然后我就直接判断若min_n + max_n > min_n * log(max_n)则采用二分,否则采用单调的方式,然后成功将我跑了36秒的代码拉回了7秒······然后我就再也卡不下去了······当时本来已经想的,卡的了多少就多少吧······结果直接过掉了,再次感谢出题人······至于复杂度证明嘛,据说是m * sqrt(m) * logn,唔,感性体会下就好了······
Source:
/*
created by scarlyw
*/
#include <cstdio>
#include <string>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cmath>
#include <cctype>
#include <vector>
#include <set>
#include <queue>
#include <ctime>
const int MAXN = 100000 + 10;
int t;
int n, m, x, y;
long long ans;
int low[MAXN];
std::vector<int> edge[MAXN];
struct edges {
int u, v;
} e[MAXN << 1 | 1];
inline void read_in() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) edge[i].clear();
for (int i = 1; i <= m; ++i) {
scanf("%d%d", &x, &y), edge[x].push_back(y), edge[y].push_back(x);
e[i].u = x, e[i].v = y;
}
}
int cnt = 0;
inline void solve_first(int x, int y) {
cnt = 0;
int size = edge[y].size();
for (register int p = edge[x].size() - 1; p >= 0; --p) {
int pos = edge[x][p];
int l = -1, r = size;
while (l + 1 < r) {
int mid = l + r >> 1;
(edge[y][mid] <= pos) ? l = mid : r = mid;
}
cnt += ((~l) && (edge[y][l] == pos));
size = l + 1;
}
}
inline void solve_second(int x, int y) {
cnt = 0;
for (register int p = edge[x].size() - 1, head = edge[y].size() - 1;
p >= 0; --p) {
int pos = edge[x][p];
while (edge[y][head] > pos && head > 0) head--;
cnt += (edge[y][head] == pos);
}
}
inline void solve() {
ans = 0;
for (int i = 1; i <= n; ++i)
std::sort(edge[i].begin(), edge[i].end());
for (int i = 1; i <= m; ++i) {
int x = e[i].u, y = e[i].v;
if (edge[x].size() > edge[y].size()) std::swap(x, y);
(edge[y].size() + edge[x].size() > edge[x].size() *
low[edge[y].size()]) ? solve_first(x, y) : solve_second(x, y);
ans += (cnt ? ((long long)cnt * (long long)(cnt - 1) / 2LL) : 0);
}
std::cout << ans << '\n';
}
int main() {
// freopen("star.in", "r", stdin);
// freopen("star.out", "w", stdout);
low[0] = -1;
for (int i = 1; i < MAXN; ++i)
low[i] = (i & i - 1) ? low[i - 1] : low[i - 1] + 1;
scanf("%d", &t);
while (t--) read_in(), solve();
return 0;
}
然后来说正解,首先我们可以很轻松的发现,对于一个点,我们可以将和它相连的所有点全部打上标记,然后枚举所有和它相邻的点,然后来找同样被打了标记的点的个数以此来统计答案,这样的做法考虑如何最优,显然,中间的点的度数应该尽量的大,那么就只用枚举所有小的相连点了,考虑这样做的复杂度,应该是就是所有边两边的点中度数较小的点的度数之和,是不是感觉是nm的,现在我们来考虑证明它其实是m * sqrt(m)的,考虑我们将点分为轻点和重点,重点表示度数大于sqrt(m)的点,轻点为度数小于sqrt(m)的点,这样边就被分为了三种:
1、两个轻点之间的边:显然这样的边的条数为O(m)的,每一次枚举小于sqrt(m),那么总复杂度为O(m * sqrt(m))
2、重点与轻点之间的边:显然这样的边的条数也是O(m)的,每一次枚举也是小于sqrt(m)的,那么总复杂度依然为O(m * sqrt(m))
3、重点与重点之间的边:首先可以肯定的是,重点的个数在O(sqrt(m))级别,那么显然对于一个重点,它相连的重点的边集的总和一定是小于m的,那么一共有O(sqrt(m))个重点,那么总复杂度还是O(m * sqrt(m))的,那么至此可以证明了,总复杂度为O(m * sqrt(m)),具体的实现可以直接通过将点按照度数排序然后依次枚举即可。
Source:
/*
created by scarlyw
*/
#include <cstdio>
#include <string>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cmath>
#include <cctype>
#include <vector>
#include <set>
#include <queue>
#include <ctime>
const int MAXN = 100000 + 10;
struct point {
int id, degree;
inline bool operator < (const point &a) const {
return degree > a.degree;
}
} p[MAXN];
int n, m, x, y, t;
long long ans = 0;
bool vis[MAXN];
int tag[MAXN];
std::vector<int> edge[MAXN];
inline void read_in() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i)
edge[i].clear(), p[i].degree = 0, p[i].id = i;
for (int i = 1; i <= m; ++i) {
scanf("%d%d", &x, &y), edge[x].push_back(y), edge[y].push_back(x);
p[x].degree++, p[y].degree++;
}
}
inline void solve() {
memset(vis, false, sizeof(bool) * (n + 1));
memset(tag, 0, sizeof(int) * (n + 1)), ans = 0;
std::sort(p + 1, p + n + 1);
for (int i = 1; i <= n; ++i) {
int cur = p[i].id;
vis[cur] = true;
for (int p = 0; p < edge[cur].size(); ++p) {
int v = edge[cur][p];
tag[v] = cur;
}
for (int p = 0; p < edge[cur].size(); ++p) {
int v = edge[cur][p], cnt = 0;
if (!vis[v]) {
for (int k = 0; k < edge[v].size(); ++k)
if (tag[edge[v][k]] == cur) cnt++;
}
ans += ((long long)cnt * (long long)(cnt - 1)) / 2;
}
}
std::cout << ans << '\n';
}
int main() {
scanf("%d", &t);
while (t--) read_in(), solve();
return 0;
}