3611: [Heoi2014]大工程

这篇博客讲述了作者在解决竞赛题目3611时的经历,面对60秒的时限,作者用约3000毫秒的时间成功解答并达到了排名4的成绩。题目涉及树的直径计算,作者选择了使用动态规划(DP)而非点分治方法。文章详细阐述了如何维护DP状态,包括三个关键量的计算,并提到了解题过程中需要考虑的边界情况和推导过程。作者在结尾表达了对这类问题的审美疲劳感。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

继续水虚树。

这题60S的时限把我吓坏了。

然而3000多MS就A了,有幸到了rank 4,nice!。

一开始还在纠结是点分治还是DP,后来想想既然能DP何不DP呢。

于是就DP了。

显然要维护三个量。

ans2和ans3都好说,参照树上直径的求法就好了,次长+最长+1变形一下。

ans1的话比较麻烦,手画个图看看,目测是当前子树的边与其他子树的边对应,然后其他子树的边再和当前子树的算一下,动手推一推就好了。

然后就OK了。

果然像Clj所说,一开始还很有意思,做多了就审美疲劳了(才一两个好不好)。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int read(){
	int x=0;char ch;
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x;
}
const int N=1000000+5;
const int inf=1e9;
struct Edge{int to,next,v;}e[N<<1];
int head[N],cnt;
int siz[N],son[N],dep[N],fa[N],top[N];
int dfn[N],dfs_clock;
void ins(int u,int v){
	if(u==v)return;
	e[++cnt]=(Edge){v,head[u],dep[v]-dep[u]};head[u]=cnt;
}
void dfs1(int u){
	siz[u]=1;son[u]=0;dfn[u]=++dfs_clock;
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].to;if(v==fa[u])continue;
		dep[v]=dep[u]+1;fa[v]=u;
		dfs1(v);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]])son[u]=v;
	}
}
void dfs2(int u,int tp){
	top[u]=tp;
	if(son[u])dfs2(son[u],tp);
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].to;
		if(v!=son[u]&&v!=fa[u])dfs2(v,v);
	}
}
int lca(int u,int v){
	while(top[u]!=top[v]){
		if(dep[top[u]]>=dep[top[v]])u=fa[top[u]];
		else v=fa[top[v]];
	}
	return dep[u]<dep[v]?u:v;
}
ll sum[N],ans1;
int mx[N],mi[N],ans2,ans3; 
bool build[N]; 
void dp(int u){
	siz[u]=build[u];
	mx[u]=build[u]?0:-inf;
	mi[u]=build[u]?0:inf;
	sum[u]=0;
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].to;
		dp(v);
		ans1+=(sum[u]+siz[u]*e[i].v)*siz[v]+sum[v]*siz[u];
		siz[u]+=siz[v];
		sum[u]+=sum[v]+siz[v]*e[i].v;
		ans2=min(ans2,mi[u]+mi[v]+e[i].v);
		ans3=max(ans3,mx[u]+mx[v]+e[i].v);
		mi[u]=min(mi[u],mi[v]+e[i].v);
		mx[u]=max(mx[u],mx[v]+e[i].v);
	}
	head[u]=0;
}
int h[N];
int st[N],tp;
bool cmp(int i,int j){
	return dfn[i]<dfn[j];
}
void solve(){
	int k=read();
	for(int i=1;i<=k;i++){
		h[i]=read();
		build[h[i]]=true;
	}
	sort(h+1,h+1+k,cmp);
	tp=cnt=0;
	st[++tp]=1;
	for(int i=1;i<=k;i++){
		int now=h[i],f=lca(now,st[tp]);
		if(f==st[tp]){st[++tp]=now;continue;}
		while(f==lca(now,st[tp-1])){
			ins(st[tp-1],st[tp]);
			tp--;f=lca(now,st[tp]);
		}
		ins(f,st[tp]);
		st[tp]=f;st[++tp]=now;
	}
	while(--tp)ins(st[tp],st[tp+1]);
	ans1=0;ans2=inf;ans3=-inf;
	dp(1);
	printf("%lld %d %d\n",ans1,ans2,ans3);
	for(int i=1;i<=k;i++)build[h[i]]=false;
}
int main(){
	int n=read();
	int u,v;
	for(int i=1;i<n;i++){
		u=read();v=read();
		ins(u,v);ins(v,u);
	}
	dfs1(1);dfs2(1,1);
	memset(head,0,sizeof(head));
	int q=read();
	while(q--)solve();
	return 0;
}
	


### 关于蓝桥杯 C/C++ 学生 A 组竞赛的相关题解与资料 #### 1. **备赛经验** 对于蓝桥杯 C/C++ 学生 A 组的比赛,参赛者需要具备扎实的基础知识以及丰富的实战经验。一位退役的 HEOI 和 CCPC 选手分享了他的备赛心得[^1]。他强调了以下几个方面的重要性: - 掌握核心知识点并能够熟练转化为代码实现。 - 注重算法和数据结构的学习,同时适当了解一些框架的应用场景。 #### 2. **常见问题分析** 在第十四届蓝桥杯比赛中,有参赛者提到自己虽然掌握了较多的知识点,但在实际编码过程中遇到了困难[^2]。这种现象表明理论学习与实践应用之间存在差距,因此建议通过量练习来弥补这一不足之处。 #### 3. **具体题目解析** 以下是部分经典试题及其解答思路: ##### (1) 填空类问题 以某一年的一道填空题为例,可以通过暴力枚举的方式快速得出答案。例如,在给定范围内统计符合条件的整数数量时,可以采用如下方法完成计算[^3]: ```cpp #include <bits/stdc++.h> using namespace std; int main(){ int ans = 0; for(int i=1;i<=100000000;++i){ int cnt=0; for(int j=i;j;j/=10)cnt++; if(cnt&1) continue; int sum=0,now=0; for(int j=i;j;j/=10){ now++; if(now<=cnt/2) sum += j % 10; else sum -= j % 10; } if(!sum) ans++; } cout << ans << endl; } ``` ##### (2) 动态规划 / 搜索 类型 另一典型问题是关于动态规划或者搜索策略的选择。这类题目通常要求设计状态转移方程或构建合理的搜索空间。例如,“有奖问答”类型的题目可能涉及复杂的路径探索或多阶段决策优化。 ##### (3) 数据结构综合运用 某些复杂度较高的题目则考验选手对高级数据结构的理解程度。比如曾经有一道需要用到三维树状数组进行区间修改操作的问题,尽管可以用暴力法解决,但从效率角度考虑显然不是最佳方案[^4]。 #### 4. **推荐资源** 为了更好地准备比赛,可以从以下几个方向入手收集材料: - 参考历年真题及官方发布的标准答案; - 加入相关论坛社区交流心得体会; - 利用在线平台刷题巩固所学概念; --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值