洛谷 P4665 [BalticOI 2015]Network

你有一棵 $ n $ 个节点的树,你可以在树上加一些边,使这棵树变成一张无重边、自环的图,且删掉任意一条边它仍然联通。求最少要加多少条边,要求输出方案



$ solution: $

居然想出来了,但是代码十分囧长,写了一个小时。

  1. (删掉任意一条边图仍然联通)说明每一条边都至少属于一个环!这样删掉这条边后,它所连的两个节点可以通过环上另一条路径相连!
  2. 然后每一个叶子节点都必定会被连一条边!因为叶子节点本身只被一条边连向它父亲,这个叶子节点若不被加边,我们只要删去它和它父亲之间的那一条边,他就与树不连通了!

根据上面两个性质,我们不难想出一种加边方案(不一定最优):先随便指定一个根节点,然后从每个叶子节点向根节点连边,这样所有叶子节点向根节点的路径(一定包含树上所有路径)都至少属于一个环!

然后我们考虑怎么优化这个方案,首先根据性质2我们可以得出另一个结论:加边数目的下界是(叶子节点数除以2向上取整)。我们发现相比从每个叶子节点向根节点连边,如果我们存在两个叶子节点他们之间的简单路径包含根节点,我们只要将两个叶子节点连边,这条边一条可以顶两条边。于是我们考虑能不能将叶子节点两两匹配,使得边数优化成原来的一半,然后发现这不就是边数下界?我们可以让叶子节点两两匹配得到这个下界吗?

答案是显然的(好吧只是博主自己讲不清而已QAQ),首先自己画图可以发现,一定存在一个节点,它的儿子节点所含子树包含一些叶子节点,且包含叶子节点最多的那个儿子它所含叶子节点的数目小于总叶子节点数的一半!这个我们可以用二次扫描和换根法证明!

然后我们可以跑一遍树找到一个根节点,用一个优先队列维护根节点的儿子它们包含的叶子节点数,然后取出数目最大的两个儿子,将它们随便包含的一个叶子节点连边,然后删除两个叶子,再将两个儿子放回优先队列。这样不断重复,即可构造一种下界方案!

$ upd: $ 更新一种 $ O(n) $ 做法, 这种做法就是将上一段改一改即可,我们上一段优先队列讲了这么多,无非就是想让两个叶子节点不在同一个根的儿子内(就是要找到一种叶子节点的匹配方法,使得两个叶子节点之间的路径包括根节点)。现在有一种更简洁的算法:找到一个根节点(根节点的儿子的最大叶子节点数不超过总数的一半),这样我们再跑一边 $ dfs $ 找到整棵树的 $ dfs $ 序(序列里属于同一个儿子的叶子节点一定在一块),然后将序列从中间开始分开,第一个和 $ mid+1 $ 匹配,第二个和 $ mid+2 $ 匹配,以此类推,因为最大叶子节点数不超过总数的一半,这样匹配的两个叶子节点一定不在同一个儿子内!这样就能 $ O(n) $ 做完了!(这个对应后面的 $ code2 $ )

注意:洛谷的SPJ是有问题的,它会先判断所有连边是否都在叶子节点之间!


$ code1: $

#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set> #define ll long long
#define db double
#define rg register int using namespace std; int n,rt,op;
int tot,top;
int a[500005];
int sz[500005];
int da[500005];
int qi[500005]; struct su{
int to,next;
}b[1000005];
int tou[500005]; struct pi{
int x,y;
inline bool operator <(const pi &z)const{
return x<z.x;
}
};
priority_queue<pi> q; inline int qr(){
register char ch; register bool sign=0; rg res=0;
while(!isdigit(ch=getchar()))if(ch=='-')sign=1;
while(isdigit(ch))res=res*10+(ch^48),ch=getchar();
if(sign)return -res; else return res;
} inline void dfs(int i,int fa){ //找到根节点
rg big=0;
for(rg j=tou[i];j;j=b[j].next){
rg to=b[j].to;
if(to==fa)continue;
dfs(to,i); sz[i]+=sz[to];
big=max(big,sz[to]);
}big=max(big,tot-sz[i]);
if(big<=tot/2)rt=i;
} inline void get(int i,int v,int fa){ //给所有叶子染色
if(a[i]==1){
da[i]=qi[v]; //链式前向星存储
qi[v]=i; sz[i]=1;
return ;
} sz[i]=0;
for(rg j=tou[i];j;j=b[j].next){
rg to=b[j].to;
if(to==fa)continue;
get(to,v,i); sz[i]+=sz[to];
}
} int main(){
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
n=qr(); op=qr();
for(rg i=1;i<n;++i){
rg x=qr(),y=qr(); ++a[x]; ++a[y];
b[++top]=su{y,tou[x]}; tou[x]=top;
b[++top]=su{x,tou[y]}; tou[y]=top;
if(a[x]>a[rt])rt=x; //找一个度数不为1的节点
}
for(rg i=1;i<=n;++i)
if(a[i]==1)++tot,sz[i]=1;
printf("%d\n",(tot+1)/2);
if(op==0)return 0;
dfs(rt,0);
for(rg i=tou[rt];i;i=b[i].next){
rg to=b[i].to; get(to,to,rt);
q.push(pi{sz[to],to}); //将根节点儿子和它所含叶子数目加入队列
}
while(!q.empty()){
pi x=q.top(); q.pop();
if(q.empty()){ //可能最后还剩一个叶子
printf("%d %d\n",qi[x.y],rt); //将它和根节点连即可
return 0;
}
pi y=q.top(); q.pop(); //取出含有叶子节点数最多的两个
printf("%d %d\n",qi[x.y],qi[y.y]); //将两个叶子连边
qi[x.y]=da[qi[x.y]]; qi[y.y]=da[qi[y.y]]; //删除两个叶子
if(--x.x>0)q.push(x); //将节点放回
if(--y.x>0)q.push(y);
}
return 0;
}

$ code2: $

#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set> #define ll long long
#define db double
#define rg register int using namespace std; int n,rt;
int top,tt;
int a[500005];
int f[500005]; struct su{
int to,next;
}b[1000005];
int tou[500005]; inline int qr(){
register char ch; register bool sign=0; rg res=0;
while(!isdigit(ch=getchar()))if(ch=='-')sign=1;
while(isdigit(ch))res=res*10+(ch^48),ch=getchar();
if(sign)return -res; else return res;
} inline void dfs(int i,int fa){
if(a[i]==1){f[++tt]=i; return ;}
for(rg j=tou[i];j;j=b[j].next)
if(b[j].to!=fa) dfs(b[j].to,i);
} int main(){ n=qr();
for(rg i=1;i<n;++i){
rg x=qr(),y=qr(); ++a[x]; ++a[y];
b[++top]=su{y,tou[x]}; tou[x]=top;
b[++top]=su{x,tou[y]}; tou[y]=top;
if(a[x]>1)rt=x; //找一个度数不为1的节点
} dfs(rt,0);
printf("%d\n",(tt+1)/2);
for(rg i=1;i<=tt/2;++i){
if(f[i]>f[tt/2+i])swap(f[i],f[tt/2+i]);
printf("%d %d\n",f[i],f[tt/2+i]);
} if(tt&1)printf("%d %d\n",f[tt/2],f[tt]);
return 0;
}

洛谷 P4665 [BalticOI 2015]Network的更多相关文章

  1. 洛谷 2679 [NOIP 2015] 子串

    题目戳这里 一句话题意 给你两个字符串A,B从A中取出K个不重合子串(顺序与在A中顺序相同)组成B,问有多少种方案? Solution 话说重打还是出各种错误也是醉了 先看题目,因为答案与A串,B串和 ...

  2. 洛谷 P2678 [ NOIP 2015 ] 跳石头 —— 二分答案

    题目:https://www.luogu.org/problemnew/show/P2678 二分答案. 代码如下: #include<iostream> #include<cstd ...

  3. 洛谷P3178[HAOI]2015 树上操作

    题目 树剖裸题,这个题更可以深刻的理解树剖中把树上的节点转换为区间的思想. 要注意在区间上连续的节点,一定是在一棵子树中. #include <bits/stdc++.h> #define ...

  4. 洛谷 P4663 - [BalticOI 2008]魔法石(dp)

    题面传送门 A:我该是有多无聊来写这种题的题解啊 B:大概是因为这题题解区里没有题解所以我来写一篇了,说明我有高尚的济世情怀(大雾 跑题了跑题了 首先看到字典序第 \(i\) 小小可以自然地想到按位决 ...

  5. 洛谷 P6573 [BalticOI 2017] Toll 题解

    Link 算是回归OI后第一道自己写的题(考CSP的时候可没回归) 写篇题解纪念一下 题目大意: \(n\) 个点,\(m\) 条单向边,每条边的两端点 \(x\),\(y\)必定满足 \(\left ...

  6. 洛谷P2668 斗地主==codevs 4610 斗地主[NOIP 2015 day1 T3]

    P2668 斗地主 326通过 2.6K提交 题目提供者洛谷OnlineJudge 标签搜索/枚举NOIp提高组2015 难度提高+/省选- 提交该题 讨论 题解 记录 最新讨论 出现未知错误是说梗啊 ...

  7. POJ1236或洛谷2746或洛谷2812 Network of Schools

    POJ原题链接 洛谷2746原题链接 洛谷2812(加强版)原题链接 显然在强连通分量里的所有学校都能通过网络得到软件,所以我们可以用\(tarjan\)求出强连通分量并缩点,统计缩点后每个点的入度和 ...

  8. 2018.10.30 一题 洛谷4660/bzoj1168 [BalticOI 2008]手套——思路!问题转化与抽象!+单调栈

    题目:https://www.luogu.org/problemnew/show/P4660 https://www.lydsy.com/JudgeOnline/problem.php?id=1168 ...

  9. 洛谷P2812校园网络【Network of Schools加强版】

    题目背景 浙江省的几所\(OI\)强校的神犇发明了一种人工智能,可以\(AC\)任何题目,所以他们决定建立一个网络来共享这个软件.但是由于他们脑力劳动过多导致全身无力身体被\(♂\)掏\(♂\)空,他 ...

随机推荐

  1. Type.MakeGenericType 方法 (Type[]) 泛型反射

    替代由当前泛型类型定义的类型参数组成的类型数组的元素,并返回表示结果构造类型的 Type 对象. 命名空间:   System程序集:  mscorlib(mscorlib.dll 中) public ...

  2. (转)深入理解Java:注解(Annotation)自定义注解入门

    向作者致敬! 转自:http://www.cnblogs.com/peida/archive/2013/04/24/3036689.html 要深入学习注解,我们就必须能定义自己的注解,并使用注解,在 ...

  3. 阶段3 1.Mybatis_12.Mybatis注解开发_1 mybatis注解开发的环境搭建

    注解开发是省了IUserDao.xml这个映射文件里面的配置 环境搭建 首先是packaging标签.输入jar 需要准备一个实体类.生成getter和setter还有toString方法 创建dao ...

  4. 阶段3 1.Mybatis_06.使用Mybatis完成DAO层的开发_8 properties标签的使用及细节

    properties 可以把数据库链接的配置放在上面的properties里面 #{占位符}的形式去引用上面的.下面的内容就是引用上面的内容的定义. 运行查询的方法测试一下 这样改造可以成功的运行程序 ...

  5. 算法初级(scala)

    来源:力扣(LeetCode)链接:https://leetcode-cn.com/problems/two-sum 1.给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为 ...

  6. centos7配置NTP时间服务器

    Network Time Protocol--NTP时间服务器,用来同步网络中各个计算机时间的协议. 通常将一台服务器配置为时间服务器,然后集群内其他服务器都来同步这台服务器的时间. 目的:集群内所有 ...

  7. 【ABAP系列】SAP ABAP 行列转换的方法

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[ABAP系列]SAP ABAP 行列转换的方法 ...

  8. awk结合数组统计

    1.统计用户登录类型 #!/bin/bashdeclare -A  shells (定义关联数组shells)while read ll   (读取/etc/passwd,ll为变量) dotype= ...

  9. 递归算法之不用乘号的乘法——用位移实现乘法(dart语言实现)

    前两天突发奇想,写一个乘法的实现,但不用乘号*.并测试一下性能如何.因此就有了下面的代码:(本文主要目的是为了玩递归和位移,因此仅限自然数) 首先,标准乘法: int commonMultiplica ...

  10. 【Python】我的第一个完整的小说爬虫

    写在开头 纪念我的第一个爬虫程序,一共写了三个白天,其中有两个上午没有看,中途遇到了各种奇怪的问题,伴随着他们的解决,对于一些基本的操作也弄清楚了.果然,对于这些东西的最号的学习方式,就是在使用中学习 ...