[??记录]AT3734 [ARC088D] Christmas Tree
command_block
2021-05-07 09:07:41
**题意** : 给定一棵 $N$ 个节点的树。用如下方法生成一棵与其相同的树:
- 首先生成 $A$ 个点数均不超过 $B$ 的链。
- 重复以下操作直到所有的点连通:
- 选择两个当前属于不同连通块的点,将这两个点**合并为一个点**,所有原来与这两个点中的至少一个点有边的点与这个新点有边。
- 将点重新标号。
求出能够生成给定树的最小的 $A$ 值,在最小化 $A$ 的基础上最小化 $B$ 值。
$n\leq 10^5$ ,时限$\texttt{2s}$。
------------
我们发现,操作相当于把两个点“重叠”。
问题相当于将若干条链重合在树上,使得每条边都被链覆盖至少一次。
由于重叠的点之前必须不连通,则任意两条链必须至多有一个点相交,即边不相交。不难发现这同时也是充分条件。
先求 $A$ ,考虑 $\rm DP$。
即 $dp_u$ 为覆盖 $u$ 子树所需的链条数目,并且儿子向父亲连出一条链。
在 $u$ 处,若有 $c_u$ 个直接儿子,则尽量将儿子两两配对,配成 $\lfloor c_u/2\rfloor$ 个对子。
若 $c_u$ 是奇数,则刚好剩下一条链向上延伸,否则还要新建一条链。
接下来求 $B$ ,显然,$B$ 越大则所需的 $A$ 越小,于是考虑二分答案,跑一个类似 [P5021 [NOIP2018 提高组] 赛道修建](https://www.luogu.com.cn/problem/P5021) 的贪心即可。
复杂度为 $O(n\log^2n)$。
```cpp
#include<algorithm>
#include<cstdio>
#include<vector>
#include<set>
#define pb push_back
#define MaxN 100500
using namespace std;
vector<int> g[MaxN];
int f[MaxN],lim,cnt;
multiset<int> s;
void dfs(int u,int fa)
{
for (int i=0,v;i<g[u].size();i++)
if (g[u][i]!=fa)dfs(g[u][i],u);
s.clear();
for (int i=0,v;i<g[u].size();i++)
if (g[u][i]!=fa&&f[g[u][i]]>1)
s.insert(f[g[u][i]]-1);
int up=0;
while(s.size()>1){
multiset<int>::iterator it,it2;
it=s.begin();
int x=*it;s.erase(it);
it2=s.lower_bound(lim-x);
if (it2!=s.end()){cnt--;s.erase(it2);}
else up=max(up,x);
}
if (!s.empty())up=max(up,*s.rbegin());
if (fa&&!up){f[u]=lim;cnt++;}
else f[u]=up;
}
int n,A;
bool chk()
{cnt=0;dfs(1,0);return cnt==A;}
int main()
{
scanf("%d",&n);
for (int i=1,u,v;i<n;i++){
scanf("%d%d",&u,&v);
g[u].pb(v);g[v].pb(u);
}
lim=n-1;chk();A=cnt;
int l=1,r=n-1;
while(l<r){
lim=(l+r)>>1;
if (chk())r=lim;
else l=lim+1;
}printf("%d %d\n",A,r);
return 0;
}
```