洛谷 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. 树链剖分&咕咕咕了好久好久的qtree3

    前言 显然qtree系列都是树链剖分辣 发现自己没有专门整理过树链剖分耶 辣么就把这篇博客魔改成树链剖分好辣(貌似除了树剖也没什么好写的) 正文 废话了辣么多终于开始了 一.树剖怎么写鸭 二.树剖有什 ...

  2. leetcode 102.Binary Tree Level Order Traversal 二叉树的层次遍历

    基础为用队列实现二叉树的层序遍历,本题变体是分别存储某一层的元素,那么只要知道,每一层的元素都是上一层的子元素,那么只要在while循环里面加个for循环,将当前队列的值(即本层元素)全部访问后再执行 ...

  3. 学习Linux的基础网站

    http://c.biancheng.net/view/726.html

  4. 你知道 Git 是如何做版本控制的吗?(转)

    总结:阅读这篇文章需要20分钟 本文是转载自 滴滴WebApp架构组 的一篇文章,文章讲解了神秘的.git目录下的一些文件,最终阐述了git是如何存储数据,及git分支的相关内容. git如何存储数据 ...

  5. Burp Suite批量网页操作

    1.打开md5解密网站,并输入“21232F297A57A5A743894A0E4A801FC3”,不要点击[Decrypt It!] 1.启动Burp Suite,并设置浏览器代理 3.点击[Dec ...

  6. JDK8新特性之一Lambda

    JDK8的新特性之一Lambda能将函数作为方法里面的参数使用. /** * JDK8新特性Lambda */ public class Test { public static void main( ...

  7. TS学习笔记----(一)基础类型

    布尔值: boolean let isDone: boolean = false; 数字: number 和JavaScript一样,TS里的所有数字都是浮点数. 支持十进制和十六进制字面量,TS还支 ...

  8. centOs 常用操作

    centos 官网下载:https://www.centos.org/download/从官网下载iso,教程:https://jingyan.baidu.com/article/1876c85279 ...

  9. 关于E980

    1. 浪商官网上面的内容貌似有点问题 来源: https://www.inspurpower.com/product/others.php?f=E980 但是wiki 里面的东西: 其实只有12cor ...

  10. linux 获取目录中详细信息 -rw-r--r--详解

    -rw-r–r– 1 root root 1313 Sep 3 14:59 test.log详解 查询目录中的内容命令 ls [选项] [文件或目录] 选项: -a 显示所有文件.包括隐藏文件 -l ...