联赛模拟测试24 联合权值·改


考完之后听说往年有一年联赛考过一道类似的题,于是一下子又以为要凉凉了,(自信一点,把以为去掉)
我们不妨先来考虑如何统计总和,我们考虑枚举每一个点作为中间点的时候对于答案的贡献,暴力枚举会(T)到飞起,我们可以记录以该点的所有出边链接的点的权值总和,之后再对总和平方,就可以得到一个部分重复的答案,重在了一个点会和自己做乘法,同时三元环的情况没有去除,前者我们在统计总和的统计记录一下平方和再减去就可以了,于是重点落在了三元环的问题上,这也是这道题的核心所在:如何求出所有的三元环.
我们考虑一种新的建图方式,对于每条双向边改为单向,由度数小的指向度数大的.如果度数一样就按编号大小排序,通过在纸上画一下我们就可以发现如果我们通过一层循环枚举起点,二维循环枚举出边,再枚举第三个点来计算每个三元环的时候每个环就一定且只会被枚举一次,于是我们减去贡献的时候需要除去这个环的六种情况,现在我们需要证明的是这样做不会像暴力一样(T)到飞起;
对于我们枚举的每一个起点我们分情况考虑,如果这个点的入度大于(sqrt m), 那么这些点的总数不会大于(sqrt m), 它们被枚举作为中点的次数最多为(m), 于是总时间复杂度为(msqrt m), 如果这个点的入度小于(sqrt m), 这样的点的个数可能会很多,但是它们被枚举作为中点的次数最多为(sqrt m), 所以总时间复杂度也不会超过(msqrt m)于是查找三元环总的时间复杂度就是(msqrt m)不会(T)
对于第二问,我们对于每个点把它的所有连接点都按权值排个序,枚举它作为中点,两层循环寻找第一个不构成三元环的两个点,直接跳出就可以了,由于三元环不会太多,我们枚举的次数也不会太多,于是我们可以通过这道题.

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <iostream>
#define reg register
typedef long long ll;
#define gc() (p1 == p2 ? (p2 = buf + fread(p1 = buf, 1, 1 << 20, stdin), p1 == p2 ? EOF : *p1++) : *p1++)
#define read() ({ register int x = 0, f = 1; register char c = gc(); while(c < '0' || c > '9') { if (c == '-') f = -1; c = gc();} while(c >= '0' && c <= '9') x = x * 10 + (c & 15), c = gc(); f * x; })
char buf[1 << 20], *p1, *p2;
const int N = 1e6 + 10;
struct Hash {//方便查找是否存在边
#define Mod 1000003
	struct Node {
		int next, x, y;
	} edge[N];
	int Head[N], tot;
	void Add(reg int x, reg int y) {
		reg int now = (x * 4 + y) % Mod;
		edge[++tot].x = x;
		edge[tot].y = y;
		edge[tot].next = Head[now];
		Head[now] = tot;
	}
	bool query(reg int x, reg int y) {
		reg int now = (x * 4 + y) % Mod;
		for(reg int i = Head[now]; i; i = edge[i].next) {
			if(edge[i].x == x && edge[i].y == y) return true;
		}
		return false;
	}
#undef Mod
} hash;
std::vector<int> ve[N], ve1[N];
ll w[N];
bool cmp(reg int x, reg int y) {
	return w[x] > w[y];
}
ll ans1, ans2;
int main() {
	freopen("link.in", "r", stdin);
	freopen("link.out", "w", stdout);
	reg int n = read(), m = read(), t = read();
	for(reg int i = 1; i <= m; ++i) {
		reg int x = read(), y = read();
		hash.Add(x, y);
		hash.Add(y, x);
		ve[x].push_back(y);//用vector便于排序
		ve[y].push_back(x);
	}
	for(reg int i = 1; i <= n; ++i)
		w[i] = read();
	int tt = 0;
	for(reg int i = 1; i <= n; ++i) {
		std::sort(ve[i].begin(), ve[i].end(), cmp);
		ll sum = 0, cnt = 0;
		for(reg int j = 0; j < ve[i].size(); ++j) {//统计总和
			sum += w[ve[i][j]];
			cnt += w[ve[i][j]] * w[ve[i][j]];
		}
		ans1 += sum * sum;
		ans1 -= cnt;
		reg bool flag = 0;
		for(reg int j = 0; j < ve[i].size(); ++j) {//统计最大值
			for(reg int k = j + 1; k < ve[i].size(); ++k) {
				if(hash.query(ve[i][k], ve[i][j])) continue;
				flag = 1;
				ans2 = std::max(ans2, w[ve[i][j]] * w[ve[i][k]]);
				break;
			}
		}
	}
	for(reg int i = 1; i <= hash.tot; i += 2) {//建新图
		reg int x = hash.edge[i].x, y = hash.edge[i].y;
		if(ve[x].size() > ve[y].size() || (ve[x].size() == ve[y].size() && x > y)) {
				ve1[y].push_back(x);
			}
		else ve1[x].push_back(y);
	}
	for(reg int i = 1; i <= n; ++i) {//查找三元环
		for(reg int j = 0; j < ve1[i].size(); ++j) {
			reg int u = ve1[i][j];
			for(reg int k = 0; k < ve1[u].size(); ++k) {
				reg int v = ve1[u][k];//去掉该三元环的所有六种情况.
				if(hash.query(v, i)) ans1 -= (w[i] * w[v] + w[i] * w[u] + w[u] * w[v]) * 2;
			}
		}
	}
	if(t != 2) printf("%lld
", ans2);
	else puts("0");
	if(t != 1) printf("%lld
", ans1);
	else puts("0");
	return 0;
}

原文地址:https://www.cnblogs.com/li-jia-hao/p/13886282.html