洛谷 P4665 [BalticOI 2015]Network
洛谷 P4665 [BalticOI 2015]Network
你有一棵 $ n $ 个节点的树,你可以在树上加一些边,使这棵树变成一张无重边、自环的图,且删掉任意一条边它仍然联通。求最少要加多少条边,要求输出方案。
$ solution: $
居然想出来了,但是代码十分囧长,写了一个小时。
- (删掉任意一条边图仍然联通)说明每一条边都至少属于一个环!这样删掉这条边后,它所连的两个节点可以通过环上另一条路径相连!
- 然后每一个叶子节点都必定会被连一条边!因为叶子节点本身只被一条边连向它父亲,这个叶子节点若不被加边,我们只要删去它和它父亲之间的那一条边,他就与树不连通了!
根据上面两个性质,我们不难想出一种加边方案(不一定最优):先随便指定一个根节点,然后从每个叶子节点向根节点连边,这样所有叶子节点向根节点的路径(一定包含树上所有路径)都至少属于一个环!
然后我们考虑怎么优化这个方案,首先根据性质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;
}