【牛客练习赛99】部分题解

A. 越狱

题意:
给定一个长度为 n n n的序列 a a a,找到一个最小的正整数 x x x,使得 m i n ( ∑ i = 1 n [ a i > x ] , ∑ i = 1 n [ a i < x ] ) min(\sum\limits_{i=1}^n[a_i>x],\sum\limits_{i=1}^n[a_i<x]) min(i=1n[ai>x],i=1n[ai<x])最大化。
数据范围:
1 ≤ n ≤ 2 × 1 0 5 , 1 ≤ a i ≤ 2 × 1 0 9 1\leq n\leq 2\times 10^5,1\leq a_i\leq 2\times 10^9 1n2×105,1ai2×109
对于 1 ≤ i < n 1\leq i<n 1i<n,都有 a i + 1 < a i + 1 a_i+1<a_{i+1} ai+1<ai+1

题解:
找到排名为 n 2 \frac{n}{2} 2n的数即可

代码:

#include<bits/stdc++.h>
using namespace std;

const int N = 100010;
int a[N], n;
int c[N];

int main()
{
    scanf("%d", &n);
    for(int i = 0; i < n; ++i) scanf("%d", &a[i]);
    nth_element(a, a + n / 2 - 1, a + n);
    printf("%d %d\n", n / 2, a[n / 2 - 1] + 1);
    return 0;
}

B. 物流

题意:
A地有 a a a 吨货物,B地有 b b b 吨货物,C地需要 c c c 吨货物,D地需要 d d d 吨货物。 1 1 1吨货物从A到C花 a c ac ac的代价,从A到D花 a d ad ad的代价,从B到C花 b c bc bc的代价,从B到D花 b d bd bd的代价。求最小代价。
注意,从A/B地到C/D地的货物是一吨一吨运输的,也就是说你可以选择A/B地的一部分货物来满足C/D地的一部分需求,但是最终C/D的需求必须全部满足。
输入只含有正整数。

数据范围:
T T T组数据, 1 ≤ T ≤ 1 0 5 1\leq T\leq 10^5 1T105
1 ≤ a , b , c , d , a c , a d , b c , c d ≤ 2 × 1 0 9 1\leq a,b,c,d,ac,ad,bc,cd\leq 2\times 10^9 1a,b,c,d,ac,ad,bc,cd2×109
a + b = c + d a+b=c+d a+b=c+d

题解:
考虑 a c , a d , b c , b d ac, ad, bc, bd ac,ad,bc,bd
如果 m a x ( a c , a d ) − m i n ( a c , a d ) > m a x ( b c , b d ) − m i n ( b c , b d ) max(ac, ad) - min(ac, ad) > max(bc, bd) - min(bc, bd) max(ac,ad)min(ac,ad)>max(bc,bd)min(bc,bd)
那么必然是优先处理掉 m i n ( a c , a d ) min(ac, ad) min(ac,ad),这样使得少一点支出,
所以这里只需要考虑优先处理掉 a c , a d , b c , b d ac,ad,bc,bd ac,ad,bc,bd中的一种即可。

代码:

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

void solve(int ca) {
	ll a, b, c, d, v[5];
	scanf("%lld%lld%lld%lld%lld%lld%lld%lld", &a, &b, &c, &d, &v[1], &v[2], &v[3], &v[4]);
	ll res = 9e18;
	// a -> c
	{
		if(a >= c) {
			res = min(res, c * v[1] + (a - c) * v[2] + b * v[4]); // a->c, a->d, b->d
		} else if(a < c) {
			res = min(res, a * v[1] + (c - a) * v[3] + d * v[4]); // a->c, b->c, b->d
		}
	}
	
	// a -> d
	{
		if(a >= d) {
			res = min(res, d * v[2] + (a - d) * v[1] + b * v[3]); // a->d, a->c, b->d
		} else if(a < d) {
			res = min(res, a * v[2] + (d - a) * v[4] + c * v[3]); // a->d, b->d, b->c
		}
	} 
	
	// b -> c
	{
		if(b >= c) {
			res = min(res, c * v[3] + (b - c) * v[4] + a * v[2]); // b->c, b->d, a->d
		} else if(b < c) {
			res = min(res, b * v[3] + (c - b) * v[1] + d * v[2]); // b->c, a->c, a->d
		}
	}
	
	// b -> d
	{
		if(b >= d) {
			res = min(res, d * v[4] + (b - d) * v[3] + a * v[1]); // b->d, b->c, a->c 
		} else if(b < d) {
			res = min(res, b * v[4] + (d - b) * v[2] + c * v[1]); // b->d, a->d, a->c 
		}
	}

	
	printf("%lld\n", res);
}

int main()
{
	int T = 1;
	scanf("%d", &T);
	for(int i = 1; i <= T; ++i) solve(i);

	return 0;
}

C. 数的和与积

题意:
给定一个 n n n ,请你将 1 1 1 n n n 中的所有整数划分为两个集合,使得第一个集合里的数的积等于第二个集合里的数的和。 输出第一个集合的数。无解输出 − 1 -1 1

数据范围:
T T T组数据, 1 ≤ T ≤ 1 0 5 1\leq T\leq 10^5 1T105
1 ≤ n ≤ 1 0 9 1\leq n\leq 10^9 1n109

题解:
模拟几个样例可以发现:
n = 2 n=2 n=2 n = 4 n=4 n=4时无解, n = 3 n=3 n=3时可以分为 sum组 1 、 2 1、2 12,mul组 3 3 3
n = 5 n=5 n=5开始,
存在有:

5
mul: 1 2 4

6
mul: 1 2 6

7
mul: 1 3 6

8
mul: 1 3 8

9
mul: 1 4 8

10
mul: 1 4 10

11 
mul: 1 5 10

12
mul: 1 5 12

可以发现三个数字依次为:

  • 1 1 1
  • ⌊ n − 1 2 ⌋ \lfloor\frac{n-1}{2}\rfloor 2n1
  • n − ( n & 1 ) n-(n\&1) n(n&1)

证明:
( a + 1 ) × ( b + 1 ) = a b + a + b + 1 (a+1)\times (b+1)=ab+a+b+1 (a+1)×(b+1)=ab+a+b+1
移项: ( a + 1 ) × ( b + 1 ) − a − b − 1 = a b × 1 (a+1)\times (b+1)-a-b-1=ab\times 1 (a+1)×(b+1)ab1=ab×1
因此只要找到 a a a b b b满足 a + b + 1 + a b = n × ( n + 1 ) 2 a+b+1+ab=\frac{n\times(n+1)}{2} a+b+1+ab=2n×(n+1)

n × ( n + 1 ) 2 \frac{n\times(n+1)}{2} 2n×(n+1)必然是一个整数,
n n n是偶数, a + 1 = n 2 a+1=\frac{n}{2} a+1=2n b + 1 = n + 1 b+1=n+1 b+1=n+1
a = n 2 − 1 a=\frac{n}{2}-1 a=2n1 b = n b=n b=n

n n n是奇数, a + 1 = n + 1 2 a+1=\frac{n+1}{2} a+1=2n+1 b + 1 = n b+1=n b+1=n
a = n + 1 2 − 1 a=\frac{n+1}{2}-1 a=2n+11 b = n − 1 b=n-1 b=n1

综合起来就是 a = ⌊ n − 1 2 ⌋ , b = n − ( n & 1 ) a=\lfloor\frac{n-1}{2}\rfloor,b=n-(n\&1) a=2n1,b=n(n&1)

n = 2 n=2 n=2时, a = 0 , b = 2 a=0,b=2 a=0,b=2 a = 0 a=0 a=0,显然不合法
n = 4 n=4 n=4时, a = 1 , b = 4 a=1,b=4 a=1,b=4,因为本身就需要选择一个 1 1 1,所以这也不合法
n = 3 n=3 n=3时, a = 1 , b = 2 a=1,b=2 a=1,b=2,此时再选择一个 1 1 1,这里就不合法了,但是可以只选择一个 3 3 3,这里的情况不符合公式,需要特判。

代码:

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
int n;

void solve() {
	scanf("%d", &n);
	if(n == 2 || n == 4) {
		puts("-1");
		return ;
	}
	
	if(n == 3) {
		puts("1\n3");
		return ;
	}
	
	printf("3\n1 %d %d\n", (n - 1) / 2, (n % 2 ? n - 1 : n));
	
}

int main()
{
	int T = 1;
	scanf("%d", &T);
	for(int i = 1; i <= T; ++i) solve();

	return 0;
}

D. 礼物

题意:
给定一棵 n n n个点的树,确定一个根使得 ∑ i = 1 n ∑ j = 1 n L C A ( i , j ) \sum\limits_{i=1}^n \sum\limits_{j=1}^n LCA(i,j) i=1nj=1nLCA(i,j) 最大。
题目保证答案唯一。
数据范围: 1 ≤ n ≤ 1 0 6 1\leq n\leq 10^6 1n106

题解:
换根 d p dp dp
这里先考虑以 1 1 1为根进行 d f s dfs dfs,求出这样的一棵有根树的子树大小。
考虑以 1 1 1为根的有根树
在这里插入图片描述

  • d o w n [ u ] down[u] down[u]表示 u u u的子树中的任意两点的LCA值之和
  • s i z [ u ] siz[u] siz[u]表示 u u u的子树大小,包括 u u u
  • 可以注意到的是树上任意一条边的两点 u u u v v v为根时,两者的 ∑ i = 1 n ∑ j = 1 n L C A ( i , j ) \sum\limits_{i=1}^n \sum\limits_{j=1}^n LCA(i,j) i=1nj=1nLCA(i,j) 差别在于:
    • u u u为根时,v及其子树uoth子树u的父亲fa对应的除去子树u的剩余子树对应的权值为: s i z [ v ] × ( n − s i z [ v ] ) × u siz[v]\times (n-siz[v])\times u siz[v]×(nsiz[v])×u
    • v v v为根时, uoth子树u的父亲fa对应的除去子树u的剩余子树v及其子树对应的权值为: s i z [ v ] × ( n − s i z [ v ] ) × v siz[v]\times (n-siz[v])\times v siz[v]×(nsiz[v])×v
      两者的其余部分是完全相同的,分别为:
  • d o w n [ v ] down[v] down[v]
  • d o w n [ o t h ] down[oth] down[oth]
  • ∑ s o n ∈ f a [ s o n ≠ u ] d o w n [ s o n ] \sum\limits_{son \in fa} [son \ne u]down[son] sonfa[son=u]down[son]

代码:

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int N = 1000010;
const int M = N << 1;
int h[N], e[M], ne[M], idx;
int n, id;
ll sum, ans;
ll siz[N];

void add(int a, int b) {
	e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

void dfs1(int u, int fa) {
	siz[u] = 1;
	for(int i = h[u]; i != -1; i = ne[i]) {
		int v = e[i];
		if(v != fa) {
			dfs1(v, u);
			sum += siz[u] * siz[v] * u;
			siz[u] += siz[v];
		}
	}
}

void dfs2(int u, int fa) {
	
	if(sum > ans) {
		ans = sum;
		id = u;
	}
	
	for(int i = h[u]; i != -1; i = ne[i]) {
		int v = e[i];
		if(v != fa) {
			ll temp = siz[v] * (n - siz[v]) * (u - v);
			sum -= temp;
			dfs2(v, u);
			sum += temp;
		}
	}
}

void solve(int ca) {
	scanf("%d", &n);
	memset(h, -1, (n + 1) * sizeof(int));
	idx = 0;
	
	for(int i = 1; i < n; ++i) {
		int a, b;
		scanf("%d%d", &a, &b);
		add(a, b);
		add(b, a);
	}
	
	dfs1(1, -1);
	ans = 0;
	dfs2(1, -1);
	
	printf("%d %lld\n", id, ans * 2 + 1ll * n * (n + 1) / 2);
}

int main()
{
	int T = 1;
//	scanf("%d", &T);
	for(int i = 1; i <= T; ++i) solve(i);

	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值