[CSP-S模拟测试]:联盟(搜索+树的直径)

题目描述

$G$国周边的$n$个小国家构成一个联盟以抵御$G$国入侵,为互相支援,他们建立了$n−1$条双向通路,使得任意两个国家可以经过通路相互到达。
当一个国家受到攻击时,所有其它国家都会沿着最短路径前往这个国家进行支援,经过每条通路所需的时间均为$1$。定义一个国家的危险程度为所有国家全部赶到需要的最短时间,联盟的危险程度为所有国家的危险程度的最大值。
为了降低危险程度,联盟决定断开一条通路并任意连接一条通路,使得危险程度尽可能小,并要求改建完成之后任意两个国家可以经过通路互相到达。他们决定让你来设计方案,你需要告知在最优方案中可能断开哪些边,并给出任意一组最优方案。


输出格式

第一行一个正整数$n$。
接下来$n−1$行每行两个正整数,表示一条$u_i,v_i$之间的边。


输出格式

输出第一行一个整数表示最小危险程度。
第二行一个整数$k$,表示可能被断开的边的数量,接下来$k$个数,表示可能断开的边的编号,按升序输出。
接下来一行四个正整数表示一组最优方案,分别表示断开和新建的边的端点,只需给出任意一组合法的方案即可。


样例

样例输入:

4
1 2
2 3
3 4

样例输出:

2
2 1 3
3 4 4 2


数据范围与提示

对于$20\%$的数据,$n\leqslant 30$。
对于$40\%$的数据,$n\leqslant 300$。
对于$60\%$的数据,$n\leqslant 3,000$。
对于$100\%$的数据,$n\leqslant 300,000$。

如果你的答案仅第一行正确,你可以获得$25\%$的分数,
如果你的答案仅前两行正确,你可以获得$50\%$的分数,
为保证得到部分分请确保提交程序的输出格式符合题目要求。


题解

联盟的危险程度其实就是树的直径,这个我们使用两遍$DFS$即可解决。

然后我们还可以用两遍$DFS$求出每棵子树的直径。

现在我们枚举断边,至于如何连边,显然我们是要将两个联通块中直径的中点相连一定最优。

这时候我们已经知道第一问和第二问了。

第三问在跑$DFS$就解决了。

时间复杂度:$\Theta(n)$。

期望得分:$100$分。

实际得分:$100$分。


代码时刻

#include<bits/stdc++.h>
using namespace std;
struct rec{int nxt,to;}e[700000];
int head[300001],cnt=1;
int n;
int len,st,ed,ban;
int dis[2][300001];
int dfn[300001],low[300001];
int dp[2][300001];
int ans[300001],sum;
void add(int x,int y)
{
	e[++cnt].nxt=head[x];
	e[cnt].to=y;
	head[x]=cnt;
}
void dfs1(int x,int fa,int tim)
{
	if(tim>=len)
	{
		len=tim;
		st=x;
	}
	for(int i=head[x];i;i=e[i].nxt)
		if(e[i].to!=fa&&(ban!=i)&&((ban^1)!=i))dfs1(e[i].to,x,tim+1);
}
void dfs2(int x,int fa,int tim)
{
	if(tim>=len)
	{
		len=tim;
		ed=x;
	}
	for(int i=head[x];i;i=e[i].nxt)
		if(e[i].to!=fa&&(ban!=i)&&((ban^1)!=i))
		{
			dfs2(e[i].to,x,tim+1);
			dis[1][i>>1]=e[i].to;
		}
}
bool dfs3(int x,int fa)
{
	dfn[++dfn[0]]=x;
	if(x==ed)return 1;
	for(int i=head[x];i;i=e[i].nxt)
		if(e[i].to!=fa&&(ban!=i)&&((ban^1)!=i)&&dfs3(e[i].to,x))
		{
			low[i>>1]=1;
			return 1;
		}
	dfn[0]--;
	return 0;
}
int dfs4(int x,int fa)
{
	int maxn=0;
	for(int i=head[x];i;i=e[i].nxt)
		if(e[i].to!=fa)
		{
			int flag=dfs4(e[i].to,x);
			dp[0][x]=max(dp[0][x],dp[0][e[i].to]);
			dp[0][x]=max(dp[0][x],flag+maxn);
			maxn=max(maxn,flag);
		}
	return maxn+1;
}
int dfs5(int x,int fa)
{
	int maxn=0;
	for(int i=head[x];i;i=e[i].nxt)
		if(e[i].to!=fa)
		{
			int flag=dfs5(e[i].to,x);
			dp[1][x]=max(dp[1][x],dp[1][e[i].to]);
			dp[1][x]=max(dp[1][x],flag+maxn);
			maxn=max(maxn,flag);
		}
	return maxn+1;
}
int main()
{
	ans[0]=1<<30;
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);add(y,x);
	}
	dfs1(1,0,0);
	len=0;
	dfs2(st,0,0);
	dfs3(st,0);
	dfs4(st,0);
	dfs5(ed,0);
	for(int i=1;i<n;i++)
	{
		if(e[i<<1].to==dis[1][i])dis[0][i]=e[i<<1|1].to;
		else dis[0][i]=e[i<<1].to;
	}
	for(int i=1;i<n;i++)
		if(!low[i])
		{
			ans[i]=max((len+1)/2+min(dp[0][e[i<<1].to]+1,dp[0][e[i<<1|1].to]+1)/2+1,len);
			ans[0]=min(ans[0],ans[i]);
		}
		else
		{
			ans[i]=max(max(dp[0][dis[1][i]],dp[1][dis[0][i]]),(dp[0][dis[1][i]]+1)/2+(dp[1][dis[0][i]]+1)/2+1);
			ans[0]=min(ans[0],ans[i]);
		}
	printf("%d\n",ans[0]);
	for(int i=1;i<n;i++)if(ans[i]==ans[0])sum++;
	printf("%d ",sum);
	for(int i=1;i<n;i++)if(ans[i]==ans[0])printf("%d ",i);
	puts("");
	for(int i=1;i<n;i++)
		if(ans[i]==ans[0])
		{
			printf("%d %d ",e[i<<1].to,e[i<<1|1].to);
			ban=i<<1;
			len=dfn[0]=0;
			dfs1(e[ban].to,0,0);
			len=0;
			dfs2(st,0,0);
			dfs3(st,0);
			printf("%d ",dfn[dfn[0]+1>>1]);
			len=dfn[0]=0;
			dfs1(e[ban^1].to,0,0);
			len=0;
			dfs2(st,0,0);
			dfs3(st,0);
			printf("%d ",dfn[dfn[0]+1>>1]);
			break;
		}
	return 0;
}


rp++

原文地址:https://www.cnblogs.com/wzc521/p/11577353.html

时间: 2024-10-30 10:45:29

[CSP-S模拟测试]:联盟(搜索+树的直径)的相关文章

[xyz模拟题]动态维护树的直径

专出神题的xyz. 支持删加边.修改点权.维护树的直径. LCT 需要额外记录子树信息.用一个堆维护. #include<cstdio> #include<cstring> #include<algorithm> #include<set> #include<vector> #include<queue> using namespace std; #define rep(i,x,y) for(i=x;i<=y;i++) #def

[考试反思]1003csp-s模拟测试58:沉淀

稳住阵脚. 还可以. 至少想拿到的分都拿到了,最后一题的确因为不会按秩合并和线段树分治而想不出来. 对拍了,暴力都拍了.挺稳的. 但是其实也有波折,险些被卡内存. 如果内存使用不连续或申请的内存全部使用的话,切记计算内存,一点都不能开大. T1: 直接根号筛,拿map也能过. 遍历map直接begin和end啊... 1 #include<cstdio> 2 int Cnt[202]; 3 struct hash_map{ 4 int cnt,fir[10000020],l[6666666],

模拟测试(vj)

做这份模拟测试,已经崩溃了,英文看不懂,题意理解错.到结束了只a了第一题,人生陷入了低谷,于是花了一天的时间终于把不会的弄明白了,在这里写一份总结~ T1,简单的模拟,如果打枪打中一支鸟,将这个位置设为0,并向两边扩散,注意这个位置一定要有鸟. 代码~ #include<bits/stdc++.h> using namespace std; int a[30000]; int n,m; int main() { cin>>n; for(int i=1;i<=n;i++) ci

2018冬令营模拟测试赛(三)

2018冬令营模拟测试赛(三) [Problem A]摧毁图状树 试题描述 输入 见"试题描述" 输出 见"试题描述" 输入示例 见"试题描述" 输出示例 见"试题描述" 数据规模及约定 见"试题描述" 题解 这题没想到贪心 QwQ,那就没戏了-- 贪心就是每次选择一个最深的且没有被覆盖的点向上覆盖 \(k\) 层,因为这个"最深的没有被覆盖的点"不可能再有其它点引出的链覆盖它了,而它又

2018冬令营模拟测试赛(十九)

2018冬令营模拟测试赛(十九) [Problem A]小Y 试题描述 输入 见"试题描述" 输出 见"试题描述" 输入示例 见"试题描述" 输出示例 见"试题描述" 数据规模及约定 见"试题描述" 题解 目前未知. 这题目前就能做到 \(O(n \sqrt{M} \log n)\),其中 \(M\) 是逆序对数,然而会被卡 \(T\):当然这题暴力可以拿到和左边那个算法一样的分数,只要暴力加一个剪枝:当左

2018-10-25 模拟测试题解

目录 问题 A: 魏传之长坂逆袭 题目描述 输入 输出 样例输入 样例输出 题解 问题 B: 蜀传之单刀赴会 题目描述 [问题描述] 输入 输出 样例输入 样例输出 题解 问题 C: 吴传之火烧连营 [题目背景] [问题描述] 输入 输出 样例输入 样例输出 [样例解释] [数据规模和约定] 题解 本篇题解也发表于zwcblog作者是同一个人 问题 A: 魏传之长坂逆袭 题目描述 众所周知,刘备在长坂坡上与他的一众将领各种开挂,硬生生从曹操手中逃了出去,随后与孙权一起火烧赤壁.占有荆益.成就霸业

noip模拟测试21

T1:折纸 这道写崩我也是没话说…… 模拟就完了,记录每次的折叠点,每次将之前的都扫一遍就完了 1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<cmath> 5 #include<algorithm> 6 #include<cstdlib> 7 #define ll long long 8 using namespace std; 9 con

[考试反思]0929csp-s模拟测试55:沦陷

菜得过分. 面对T1的大板子不知所措,然后T2的贪心不小心把排序语句删了... T1这种大模板啊...其实我是觉得我能打出来的,然后先用一个小时码了一个2k. 然后做T2想贪心就出来了.十分钟码完T3暴力之后回T1打对拍瞬间爆炸. 于是又重新打了一个2k,WA0.对拍发现. 然后考试就没几分钟了交暴力走了. 不要打完就跑,记得早点对拍改进思路. T1: 的确是挺裸的线段树.离散化或者权值线段树都可以. 但是考场上两个都打出来都死了. 最后用离散化A的. 1 #include<cstdio> 2

[考试反思]1002csp-s模拟测试56:凌乱

放假回来状态回升??(玩够了-但是稍困) T1打的不完全对,但是过掉了.很快的想到了二分吧喇叭啦.. 然后T2也挺快想出来了但是挂细节没发现,考试快结束的时候才发现出锅了. 改了过来是正解,但是出题人无良卡了线段树强制树状数组,T了一个子任务,卡常到飞起. T3暴力没什么问题. 卡常是一种习惯.要注意题目数据范围观察是否卡常. T1: 所有的决策都是一条一次函数. 分两类,斜率正或斜率非负. 如果第二类的直线里有在T=0时符合要求的,那么答案就是0,所以check(0)一下. 如果非负的直线都在