NOIP模拟(10.30)T3 星星

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/scar_lyw/article/details/78397588

星星

题目背景:

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;
}
阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页