BZOJ 3611 HEOI 2014 大工程 LCA单调性

题目大意:给出一棵树,每次询问一些节点,当把这些节点连接起来,使得每两个询问的点之间有一条边,共有k*(k - 1)条边。问这些边中,长度的总和是多少,最短的一条边是多少,最长的一条边是多少。保证询问的点的总数是O(n)级别。

思路:利用LCA单调性,每次询问的时候重新建树,在这棵树上做DP,使得总体时间复杂度降到O(nlogn)。

树形DP我写的都要麻烦死了。。听了正解之后简直想吐血。。

我的做法是维护四个数组,sum,size,_min,_max,分别表示以当前节点为根节点的子树中的所有关键点到根节点的距离的总和,共有多少个关键点,距离根节点最近的关键点的距离,距离跟节点最远的关键点的距离。此外,在做DP的同时,除了最值还要记录一下次值,用__min和__max表示。记录一个全局变脸来表示最终答案。DP方程(y表示x的一个子树的根节点):

size[x] = ∑size[y] + super[x];

sum[x] = ∑(sum[y] + length * size[y]);

_min[x] = min{_min[y] + length}

_max[x] = max{_max[y] + length}

注意还要更新一下次值

更新答案的表达式:

ans += ∑((sum[y] + length * size[y]) * (size[x] - size[y]));

ans_min = min(ans_min,_min[x] + __min);

ans_max = max(ans_max,_max[x] + __max);

没了。。记得开long long

其实完全不用记录次值,只需要两两子树合并就可以了,然后都取最值。

CODE:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define MAX 1000010
#define INF (1ll << 60)
using namespace std;

int points,asks;
int head[MAX],total;
int next[MAX << 1],aim[MAX << 1];

int pos[MAX],cnt;
int deep[MAX],father[MAX][20];

inline void Add(int x,int y)
{
	next[++total] = head[x];
	aim[total] = y;
	head[x] = total;
}

void DFS(int x,int last)
{
	pos[x] = ++cnt;
	deep[x] = deep[last] + 1;
	father[x][0] = last;
	for(int i = head[x]; i; i = next[i]) {
		if(aim[i] == last)	continue;
		DFS(aim[i],x);
	}
}

inline int GetLCA(int x,int y)
{
	if(deep[x] < deep[y])	swap(x,y);
	for(int i = 19; ~i ; --i)
		if(deep[father[x][i]] >= deep[y])
			x = father[x][i];
	if(x == y)	return x;
	for(int i = 19; ~i; --i)
		if(father[x][i] != father[y][i])
			x = father[x][i],y = father[y][i];
	return father[x][0];
}

void MakeTable()
{
	for(int j = 1; j <= 19; ++j)
		for(int i = 1; i <= points; ++i)
			father[i][j] = father[father[i][j - 1]][j - 1];
}

long long ans_min,ans_max,ans;

struct Graph{
	int head[MAX],v[MAX],T,total;
	int next[MAX],aim[MAX];
	long long length[MAX];

	long long size[MAX],sum[MAX];
	long long _min[MAX],_max[MAX];
	int super[MAX];

	void Reset() {
		total = 0;
		++T;
	}
	void Set(int x) {
		super[x] = T;
	}
	inline void Add(int x,int y,long long len) {
		if(v[x] != T) {
			v[x] = T;
			head[x] = 0;
		}
		next[++total] = head[x];
		aim[total] = y;
		length[total] = len;
		head[x] = total;
	}
	void TreeDP(int x) {
		size[x] = 0;
		sum[x] = 0;
		_min[x] = super[x] == T ? 0:INF;
		_max[x] = super[x] == T ? 0:-INF;
		long long __min = INF,__max = -INF;
		if(v[x] != T)
			v[x] = T,head[x] = 0;
		for(int i = head[x]; i; i = next[i]) {
			TreeDP(aim[i]);
			size[x] += size[aim[i]];
			sum[x] += sum[aim[i]] + length[i] * size[aim[i]];
			if(_max[aim[i]] + length[i] > _max[x]) {
				__max = _max[x];
				_max[x] = _max[aim[i]] + length[i];
			}
			else if(_max[aim[i]] + length[i] > __max)
				__max = _max[aim[i]] + length[i];
			if(_min[aim[i]] + length[i] < _min[x]) {
				__min = _min[x];
				_min[x] = _min[aim[i]] + length[i];
			}
			else if(_min[aim[i]] + length[i] < __min)
				__min = _min[aim[i]] + length[i];
		}
		for(int i = head[x]; i; i = next[i]) {
			ans += (sum[aim[i]] + length[i] * size[aim[i]]) * (size[x] - size[aim[i]]);
			if(super[x] == T)
				ans += (sum[aim[i]] + length[i] * size[aim[i]]);
		}
		ans_min = min(ans_min,_min[x] + __min);
		ans_max = max(ans_max,_max[x] + __max);
		size[x] += super[x] == T;
	}
}graph;

struct Complex{
	int x,pos;

	Complex(int _,int __):x(_),pos(__) {}
	Complex() {}
	bool operator <(const Complex &a)const {
		return pos < a.pos;
	}
}src[MAX];

int stack[MAX];

int main()
{
	cin >> points;
	for(int x,y,i = 1; i < points; ++i) {
		scanf("%d%d",&x,&y);
		Add(x,y),Add(y,x);
	}
	DFS(1,0);
	MakeTable();
	cin >> asks;
	for(int cnt,j = 1; j <= asks; ++j) {
		scanf("%d",&cnt);
		for(int i = 1; i <= cnt; ++i)
			scanf("%d",&src[i].x),src[i].pos = pos[src[i].x];
		sort(src + 1,src + cnt + 1);
		graph.Reset();
		int top = 0;
		stack[++top] = 1;
		for(int i = 1; i <= cnt; ++i) {
			int lca = GetLCA(stack[top],src[i].x);
			while(deep[stack[top]] > deep[lca]) {
				if(deep[stack[top - 1]] <= deep[lca]) {
					int away = stack[top--];
					if(stack[top] != lca)	stack[++top] = lca;
					graph.Add(stack[top],away,abs(deep[stack[top]] - deep[away]));
					break;
				}
				graph.Add(stack[top - 1],stack[top],abs(deep[stack[top - 1]] - deep[stack[top]])),--top;
			}
			if(stack[top] != src[i].x)	stack[++top] = src[i].x;
			graph.Set(src[i].x);
		}
		while(top != 1)
			graph.Add(stack[top - 1],stack[top],abs(deep[stack[top - 1]] - deep[stack[top]])),--top;
		ans_min = INF,ans_max = 0,ans = 0;
		graph.TreeDP(1);
		printf("%lld %lld %lld\n",ans,ans_min,ans_max);
	}
	return 0;
}
时间: 2024-10-11 11:34:57

BZOJ 3611 HEOI 2014 大工程 LCA单调性的相关文章

BZOJ 3613 HEOI 2014 南园满地堆轻絮 二分+贪心

题目大意 给出一个数字序列,要求将这个数字序列变成单调不降的序列.若原来的数字是A[i],变化之后的数字是B[i],那么花费是|A[i]?B[i]| .求出一种方案,使得最大的花费最小. 思路 一眼就能看出是二分,然后贪心什么的随便yy一下就行了. CODE #define _CRT_SECURE_NO_WARNINGS #include <cstdio> #include <cstring> #include <iostream> #include <algor

bzoj 3611: [Heoi2014]大工程

Description 国家有一个大工程,要给一个非常大的交通网络里建一些新的通道. 我们这个国家位置非常特殊,可以看成是一个单位边权的树,城市位于顶点上. 在 2 个国家 a,b 之间建一条新通道需要的代价为树上 a,b 的最短路径. 现在国家有很多个计划,每个计划都是这样,我们选中了 k 个点,然后在它们两两之间 新建 C(k,2)条 新通道. 现在对于每个计划,我们想知道: 1.这些新通道的代价和 2.这些新通道中代价最小的是多少 3.这些新通道中代价最大的是多少 Input 第一行 n

大工程(bzoj 3611)

Description 国家有一个大工程,要给一个非常大的交通网络里建一些新的通道. 我们这个国家位置非常特殊,可以看成是一个单位边权的树,城市位于顶点上. 在 2 个国家 a,b 之间建一条新通道需要的代价为树上 a,b 的最短路径. 现在国家有很多个计划,每个计划都是这样,我们选中了 k 个点,然后在它们两两之间 新建 C(k,2)条 新通道. 现在对于每个计划,我们想知道: 1.这些新通道的代价和 2.这些新通道中代价最小的是多少 3.这些新通道中代价最大的是多少 Input 第一行 n

【BZOJ】【3611】【HEOI2014】大工程

虚树+树形DP 本题100W的点数……不用虚树真的好吗…… Orz ZYF 我的感悟: dp的过程跟SPOJ 1825 FTOUR2 的做法类似,依次枚举每个子树,从当前子树和之前的部分中各找一条最长(短)路径更新答案,再把这个子树的最短路径加入到x节点中去(之前算过的部分)这样就实现了枚举所有可能的最优情况!而且复杂度很低!避免了两两之间的枚举…… 1 /************************************************************** 2 Probl

[Bzoj3611][Heoi2014]大工程(虚树)

3611: [Heoi2014]大工程 Time Limit: 60 Sec  Memory Limit: 512 MBSubmit: 2000  Solved: 837[Submit][Status][Discuss] Description 国家有一个大工程,要给一个非常大的交通网络里建一些新的通道. 我们这个国家位置非常特殊,可以看成是一个单位边权的树,城市位于顶点上. 在 2 个国家 a,b 之间建一条新通道需要的代价为树上 a,b 的最短路径. 现在国家有很多个计划,每个计划都是这样,

【BZOJ3611】大工程(虚树,动态规划)

[BZOJ3611]大工程(虚树,动态规划) 题面 BZOJ Description 国家有一个大工程,要给一个非常大的交通网络里建一些新的通道. 我们这个国家位置非常特殊,可以看成是一个单位边权的树,城市位于顶点上. 在 2 个国家 a,b 之间建一条新通道需要的代价为树上 a,b 的最短路径. 现在国家有很多个计划,每个计划都是这样,我们选中了 k 个点,然后在它们两两之间 新建 C(k,2)条 新通道. 现在对于每个计划,我们想知道: 1.这些新通道的代价和 2.这些新通道中代价最小的是多

LibreOJ #2219. 「HEOI2014」大工程

二次联通门 : LibreOJ #2219. 「HEOI2014」大工程 /* LibreOJ #2219. 「HEOI2014」大工程 虚树 + dp 对于每次的关键点建好虚树后 考虑树形dp dp[i] 表示在以i为根的子树路径总长 size[i] 表示在以i为根的子树中关键点的个数 maxn为以i为根的子树中到关键点到根最长的路径长 ans1自己推一推就好 对于ans2 如果i是关键点,则直接用maxn[i]更新答案 否则用maxn[i]+maxn[child[i]]+dis(i,son[

刷题总结——大工程(bzoj3611)

题目: Description 国家有一个大工程,要给一个非常大的交通网络里建一些新的通道. 我们这个国家位置非常特殊,可以看成是一个单位边权的树,城市位于顶点上. 在 2 个国家 a,b 之间建一条新通道需要的代价为树上 a,b 的最短路径. 现在国家有很多个计划,每个计划都是这样,我们选中了 k 个点,然后在它们两两之间 新建 C(k,2)条 新通道. 现在对于每个计划,我们想知道: 1.这些新通道的代价和 2.这些新通道中代价最小的是多少 3.这些新通道中代价最大的是多少 Input 第一

[HEOI2014]大工程

题目描述 国家有一个大工程,要给一个非常大的交通网络里建一些新的通道. 我们这个国家位置非常特殊,可以看成是一个单位边权的树,城市位于顶点上. 在 2 个国家 a,b 之间建一条新通道需要的代价为树上 a,b 的最短路径. 现在国家有很多个计划,每个计划都是这样,我们选中了 k 个点,然后在它们两两之间 新建 C(k,2)条 新通道.现在对于每个计划,我们想知道: 1.这些新通道的代价和 2.这些新通道中代价最小的是多少 3.这些新通道中代价最大的是多少 输入输出格式 输入格式: 第一行 n 表