洛谷 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. MVC、MVP 和 MVVM

    MVC Model–View–Controller 模型:管理应用程序的数据.逻辑和规则 视图:展示数据(可以直接从模型中获取数据) 控制器:接收输入并将其转化成模型和视图的命令 MVP Model– ...

  2. Advanced Message Queuing Protocol ( 1 ) 概述

    The Advanced Message Queuing Protocol (AMQP)是一个标准开放的应用层的消息中间件(Message Oriented Middleware)协议.AMQP定义了 ...

  3. Linux_LEMP

    目录 目录 LEMP Nginx mysql PHP php-fpm Script LEMP Nginx是一个高性能的HTTP和反向代理服务器,也是一个 IMAP/POP3/SMTP 服务器,因它的稳 ...

  4. oracle ogg 单实例双向复制搭建(oracle-oracle)--Oracle GoldenGate

    oracle ogg 单实例双向复制搭建(oracle-oracle)--Oracle GoldenGate --继昨天的测试,这一篇实施单实例双向复制(完全重新搭建) --环境不变 db1,db2( ...

  5. ARTS-2

    ARTS的初衷 Algorithm:主要是为了编程训练和学习.每周至少做一个 leetcode 的算法题(先从Easy开始,然后再Medium,最后才Hard).进行编程训练,如果不训练你看再多的算法 ...

  6. SPA(single page application)

    一.SPA的概述 SPA(single page application)单页面应用程序,在一个完成的应用或者站点中,只有一个完整的html页面,这个页面有一个容器,可以把需要加载的代码片段插入到该容 ...

  7. 深入理解java:2.3.2. 并发编程concurrent包 之重入锁/读写锁/条件锁

    重入锁 Java中的重入锁(即ReentrantLock)   与JVM内置锁(即synchronized)一样,是一种排它锁. ReentrantLock提供了多样化的同步,比如有时间限制的同步(定 ...

  8. Vim常用的功能命令

    一.编辑 查看行号     :set nu 删除一整行   dd 删除1到10行     :1,10d 删除所有内容     dG 当前行下插入一空行   o 撤销改动    u 查看当前行信息    ...

  9. Redis进阶:Redis的主从复制机制

    Redis进阶:Redis的主从复制机制 主从复制机制介绍 单机版的Redis存在性能瓶颈,Redis通过提高主从复制实现读写分离,提高了了Redis的可用性,另一方便也能实现数据在多个Redis直接 ...

  10. [Web 前端] 026 jQuery 初探

    目录 1. jQuery 简介 2. jQuery 的简单操作 2.1 jQuery 选择器 2.1.1 简介 2.1.2 基础选择器 2.2 过滤获取 2.3 父子关系获取 3. jQuery 元素 ...