NOIP模拟(10.30)T3 星星

来源:互联网 发布:初音未来眼药水 淘宝 编辑:程序博客网 时间:2024/05/29 13:09

星星

题目背景:

10.30 NOIP模拟T3

分析:玄学 + 复杂度分析

 

表示在台下分分钟用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;}

原创粉丝点击