「模拟赛20191019」C 推式子+贪心+树状数组

题目描述

给定一棵n个点的有根树,根节点编号为1,点有点权。

定义d(v)表示v1的路径上的边数。

定义f(v,u)v<uvu任意一个都不是另一个的祖先时为1,否则为0

定义g(v,u)vu的祖先且v的权值大于u的权值时为1,否则为0

定义h(v,u)vu的祖先且v的权值小于u的权值时为1,否则为0

你需要将点集分成两个集合AB,有m组询问,每组询问给定了集合A的大小,求下列表达式的最小值:

F(A,B)=vA,uA,vuf(v,u)+g(v,u)+vB,uB,vuh(v,u)+vAd(v)

输入

第一行两个整数n,m

接下来一行n个整数ai表示第i个点的权值。

接下来n1行,第i行两个整数v,u表示一条连接(v,u)的边。

接下来m行,每行一个整数表示|A|

输出

m行,每行一个整数表示表达式的最小值。

样例

样例输入

4 3
4 1 2 3
1 2
2 3
2 4
0
2
4

样例输出

2
2
9

数据范围

对于100%的数据,1n,m500000,0|A|n,1ai500000

比第二题又难了不少……(差评,题目难度指数式上升)

这一坨定义看起来好难受啊,考虑转化一下。

首先看f的意义,发现其实就是|A|中互不为祖先的点对数,等价于C(|A|,2)每个点在A中的祖先个数的和;d的意义是深度,也就是A中所有点在树上的祖先个数的和。

那这两个加起来是什么呢?就是C(|A|,2)+满足uA,vBuv的祖先的点对数量。

另外,我们定义一个新函数e(u,v)uv的祖先,且au=av时为1,否则为0。显然[uv的祖先]等价于g(u,v)+h(u,v)+e(u,v)。那么化一下原式:

F(A,B)=C(|A|,2)+vA,uA,vug(v,u)+vB,uB,vuh(v,u)+vA,uBg(v,u)+h(v,u)+e(v,u)

gh合并到前面去,于是gh的一半部分变成全集了:

F(A,B)=C(|A|,2)+vAg(v,u)+uBh(v,u)+vA,uBe(v,u)

然后还有一个e比较烦人,注意到若vu的祖先,且vB,uA,如果交换vug部分会变更优,h部分会变更优,e部分也会变更优,当然会优先选择祖先……所以当一个点被加入A中时,他的祖先里和它权值一样的一定早就全都被加入到里面去了。所以e可以很轻松地计算,此时每个点加入A中带来的权值已经和其他元素毫不相干了,只需要用树状数值帮助预处理一下即可。

Code:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define N 500005
#define M 1000005
#define ll long long
ll ans[N];
int n, m, A[N], tre[N], sum[N];
int tar[M], nex[M], fir[N], cnt, tim;
int fat[N], dep[N], dfn[N], out[N], val[N], idx[N];
void Read(int &p)
{
	p = 0;
	char c = getchar();
	for (; c < '0' || c > '9'; c = getchar());
	for (; c >= '0' && c <= '9'; c = getchar())p = p * 10 + c - '0';
}
void Update(int x, int v)
{
	for (int i = x; i <= N - 5; i += (i & -i))
		tre[i] += v;
}
int Getsum(int x)
{
	int ans = 0;
	for (int i = x; i; i -= (i & -i))
		ans += tre[i];
	return ans;
}
void Add(int u, int v)
{
	++cnt;
	tar[cnt] = v;
	nex[cnt] = fir[u];
	fir[u] = cnt;
}
void Dfs(int r)
{
	dfn[r] = ++tim;
	val[r] = dep[r] - Getsum(A[r]);
	Update(A[r], 1);
	for (int i = fir[r]; i; i = nex[i])
	{
		int v = tar[i];
		if (v != fat[r])
		{
			fat[v] = r;
			dep[v] = dep[r] + 1;
			Dfs(v);
		}
	}
	Update(A[r], -1);
	out[r] = tim;
};
bool cmp(int a, int b){return A[a] > A[b];}
int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++)
		Read(A[i]), idx[i] = i;
	for (int i = 1; i < n; i++)
	{
		int u, v;
		Read(u), Read(v);
		Add(u, v), Add(v, u);
	}
	Dfs(1);
	sort(idx + 1, idx + n + 1, cmp);
	for (int i = 1; i <= n; )
	{
		int x = i;
		while (x <= n && A[idx[i]] == A[idx[x]])
			x++;
		for (int k = i; k < x; k++)
			sum[idx[k]] = Getsum(out[idx[k]]) - Getsum(dfn[idx[k]] - 1);
		for (int k = i; k < x; k++)
			Update(dfn[idx[k]], 1);
		i = x;
	}
	for (int i = 1; i <= n; i++)
		val[i] -= sum[i], ans[0] += sum[i];
	sort(val + 1, val + n + 1);
	for (int i = 1; i <= n; i++)
		ans[i] = ans[i - 1] + val[i] + i - 1;
	for (; m--; )
	{
		int x;
		Read(x);
		printf("%lld\n", ans[x]);
	}
}
posted @   ModestStarlight  阅读(194)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示