牛客练习赛68 C-牛牛的无向图 (MST&边贡献)

 

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld

题目描述

牛牛有一张 n 个点,m 条边的无向图,每条边有一个边权 wiw_iwi​

我们定义一条路径的权值是这个路径包含的边的权值的最大值。

定义 d(u,v) 表示在无向图中点 u 能到达点 v 的所有路径中权值最小的路径的权值
 

现在牛牛给你 q 次询问,每次询问给出一个 L ,询问 ∑i=1n∑j=i+1n[d(i,j)≤L]\sum_{i=1}^n \sum_{j=i+1}^n [d(i,j) \leq L]∑i=1n​∑j=i+1n​[d(i,j)≤L]。其中 [C] 表示当命题 C 为真的时候为 1 否则为 0。比如 [出题人很弱]=1,[1≥2]=0[\text{出题人很弱}]=1,[1 \geq 2] = 0[出题人很弱]=1,[1≥2]=0。

 

为了防止输入过大,选手需要在自己的程序内生成要输入的所有数据,可以参考如下代码:

unsigned int SA, SB, SC; int n, m, q, LIM;
unsigned int rng61(){
    SA ^= SA << 16;
    SA ^= SA >> 5;
    SA ^= SA << 1;
    unsigned int t = SA;
    SA = SB;
    SB = SC;
    SC ^= t ^ SA;
    return SC;
}

void gen(){
    scanf("%d%d%d%u%u%u%d", &n, &m, &q, &SA, &SB, &SC, &LIM);
    for(int i = 1; i <= m; i++){
        u[i] = rng61() % n + 1;
        v[i] = rng61() % n + 1;
        w[i] = rng61() % LIM;
    }
    for(int i = 1; i <= q; i++){
        L[i] = rng61() % LIM;
    }
}

生成的 ui,vi,wiu_i,v_i,w_iui​,vi​,wi​ 表示第 条边的属性,LiL_iLi​ 表示第 次询问输入的数。

 

输入描述:

一行七个正整数 

输出描述:

一行,表示所有答案的异或和。

示例1

输入

复制5 7 5 480944053 701657892 339027200 10

5 7 5 480944053 701657892 339027200 10

输出

复制1

1

说明

 

牛客练习赛68 C-牛牛的无向图 (MST&边贡献)_第1张图片

五次询问分别是 7,4,0,4,9

答案分别是 3,3,1,3,3

异或后答案是 1。

备注:

 

1≤n≤105,1≤m,q≤5×105,1≤LIM≤1091 \leq n \leq 10^5,1 \leq m,q \leq 5 \times 10^5,1 \leq LIM \leq 10^91≤n≤105,1≤m,q≤5×105,1≤LIM≤109

题目大意:

牛客练习赛68 C-牛牛的无向图 (MST&边贡献)_第2张图片

输出所有询问的异或和。

解法:

首先MST能够使路径包含的边的权值最大值最小,这是容易观察到的性质,那么这个无向图就简化成了若干棵树。如果把树边都删掉,按照边权从小到大加回去,每条边的贡献恰好是两个点集大小的乘积,同时也能够满足第二条性质,这样求MST的过程中就能够解决所有的问题,剩下询问的答案可以离线也预处理前缀和再二分。

Accepted code

#pragma GCC optimize(3)
#include
#include
using namespace std;

#define sc scanf
#define Min(x, y) x = min(x, y)
#define Max(x, y) x = max(x, y)
#define ALL(x) (x).begin(),(x).end()
#define SZ(x) ((int)(x).size())
#define pir pair 
#define MK(x, y) make_pair(x, y)
#define MEM(x, b) memset(x, b, sizeof(x))
#define MPY(x, b) memcpy(x, b, sizeof(x))
#define lowbit(x) ((x) & -(x))
#define P2(x) ((x) * (x))

typedef long long ll;
const int Mod = 1e9 + 7;
const int N = 1e5 + 100;
const int M = 5e5 + 100;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
inline ll dpow(ll a, ll b){ ll r = 1, t = a; while (b){ if (b & 1)r = (r*t) % Mod; b >>= 1; t = (t*t) % Mod; }return r; }
inline ll fpow(ll a, ll b){ ll r = 1, t = a; while (b){ if (b & 1)r = (r*t); b >>= 1; t = (t*t); }return r; }

unsigned int SA, SB, SC; int n, m, q, LIM;
unsigned int rng61(){
	SA ^= SA << 16;
	SA ^= SA >> 5;
	SA ^= SA << 1;
	unsigned int t = SA;
	SA = SB;
	SB = SC;
	SC ^= t ^ SA;
	return SC;
}
struct node
{
	ll w;
	int u, v;
	bool operator < (const node &oth) const {
		return w < oth.w;
	}
}a[M];
int f[N], sz[N];
ll val[M], pre[M];

void Init() {
	for (int i = 1; i <= n; i++)
		f[i] = i, sz[i] = 1;
}
int Find(int x) {
	while (x != f[x])
		x = f[x];
	return x;
}
void Merge(int x, int y) {
	x = Find(x);
	y = Find(y);
	if (x == y)
		return;
	if (sz[x] > sz[y])
		swap(x, y);
	f[x] = y, sz[y] += sz[x];
}

int main()
{
	sc("%d%d%d%u%u%u%d", &n, &m, &q, &SA, &SB, &SC, &LIM);
	Init();
	for (int i = 1; i <= m; i++) {
		int u = rng61() % n + 1;
		int v = rng61() % n + 1;
		ll w = rng61() % LIM;
		a[i] = { w, u, v };
	}
	sort(a + 1, a + m + 1);

	int k = 0, pos = 0;
	for (int i = 1; i <= m; i++) {
		if (k == n - 1)
			break;
		int u = a[i].u, v = a[i].v;
		if (Find(u) == Find(v))
			continue;
		pos++;
		pre[pos] = pre[pos - 1] + 1ll * sz[Find(u)] * sz[Find(v)];  // 点对数前缀和
		val[pos] = a[i].w;
		Merge(u, v);
	}

	ll ans = 0;
	for (int i = 1; i <= q; i++) {
		int qi = rng61() % LIM;
		int it = upper_bound(val + 1, val + pos + 1, qi) - val; // 二分位置
		ans ^= pre[it - 1];
	}
	printf("%lld\n", ans);
	return 0;  // 改数组大小!!!用pair记得改宏定义!!!
}

 

你可能感兴趣的:(图论,日常训练)