Nastia Plays with a Tree
题解
简单dp
很明显,我们可以先定义
d
p
i
,
j
dp_{i,j}
dpi,j表示点
i
i
i在它的子节点中与
j
j
j个儿子间的边被保留时的子树内最大保留边数。让更改边数最小是等于让保留边数最大的。
对于每个
d
p
i
,
j
dp_{i,j}
dpi,j,我们只需要记录下它当前dp状态与它的儿子之间的连接情况与儿子状态即可。
由于一个点最多只能选择两条边,所以我们只需要找出儿子中选与不选的dp值差距最大的两个即可。
有了连接情况,我们就知道哪些链是需要被保留下来的。将这些链收尾相连即可。
跑一趟dp即可,注意加链时选取的点。
时间复杂度 O ( n ) O\left(n\right) O(n)。
源码
#include<bits/stdc++.h>
using namespace std;
#define MAXN 100005
#define lowbit(x) (x&-x)
#define reg register
#define mp make_pair
#define fir first
#define sec second
typedef long long LL;
typedef unsigned long long uLL;
const int INF=0x3f3f3f3f;
const int jzm=233;
const int mo=1e9+7;
const double Pi=acos(-1.0);
typedef pair<int,int> pii;
const double PI=acos(-1.0);
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
_T f=1;x=0;char s=getchar();
while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
x*=f;
}
int n,t,head[MAXN],tot,f[MAXN][3],stak,fa[MAXN],sta[MAXN],pre[MAXN][3],deg[MAXN];
bool link[MAXN][3];
vector<pii> ans;
struct edge{int to,nxt;}e[MAXN<<1];
struct node{int u,v;}s[MAXN];
void addEdge(int u,int v){e[++tot]=(edge){v,head[u]};head[u]=tot;}
void makeSet(int x){for(int i=1;i<=x;i++)fa[i]=i;}
int findSet(int x){return x==fa[x]?x:fa[x]=findSet(fa[x]);}
void unionSet(int a,int b){int u=findSet(a),v=findSet(b);if(u!=v)fa[u]=v;}
void dosaka(int u,int fa){
int mx1=0,v1=0,mx2=0,v2=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;if(v==fa)continue;dosaka(v,u);
int tmp1=max(f[v][0],f[v][1])+1,tmp2=max(tmp1-1,f[v][2]);
if(tmp1>tmp2&&tmp1-tmp2>mx1)mx2=mx1,v2=v1,mx1=tmp1-tmp2,v1=v;
else if(tmp1>tmp2&&tmp1-tmp2>mx2)mx2=tmp1-tmp2,v2=v;
}
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;if(v==fa)continue;int tmp=max(f[v][0],max(f[v][1],f[v][2]));
if(v==v1||v==v2)f[u][2]+=max(f[v][0],f[v][1])+1,link[v][2]=1;else f[u][2]+=tmp;
if(v==v1)f[u][1]+=max(f[v][0],f[v][1])+1,link[v][1]=1;else f[u][1]+=tmp;f[u][0]+=tmp;
if(v==v1||v==v2)pre[v][2]=f[v][0]>=f[v][1]?0:1;else pre[v][2]=(tmp==f[v][0]?0:(tmp==f[v][1]?1:2));
if(v==v1)pre[v][1]=f[v][0]>=f[v][1]?0:1;else pre[v][1]=(tmp==f[v][0]?0:(tmp==f[v][1]?1:2));
pre[v][0]=(tmp==f[v][0]?0:(tmp==f[v][1]?1:2));
}
}
void dosaka1(int u,int fa,int S){
if(u==1&&S==1)sta[++stak]=u;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;if(v==fa)continue;
if(pre[v][S]==1&&!link[v][S])sta[++stak]=v;
if(!link[v][S])ans.push_back(mp(u,v));
else unionSet(u,v);
dosaka1(v,u,pre[v][S]);
}
if(!S)sta[++stak]=u;
}
bool cmp(int x,int y){return findSet(x)<findSet(y);}
signed main(){
read(t);
while(t--){
read(n);makeSet(n);
for(int i=1,u,v;i<n;i++)read(u),read(v),addEdge(u,v),addEdge(v,u),deg[u]++,deg[v]++,s[i]=(node){u,v};dosaka(1,0);
if(f[1][0]>=f[1][1]&&f[1][0]>=f[1][2])dosaka1(1,0,0);
else if(f[1][1]>=f[1][2])dosaka1(1,0,1);else dosaka1(1,0,2);
sort(sta+1,sta+stak+1,cmp);printf("%d\n",ans.size());int id=2;
for(int i=0;i<(int)ans.size();i++){
while(id<stak&&findSet(sta[id-1])==findSet(sta[id]))id++;deg[ans[i].fir]--,deg[ans[i].sec]--;deg[sta[id]]++;deg[sta[id-1]]++;
printf("%d %d %d %d\n",ans[i].fir,ans[i].sec,sta[id-1],sta[id]);id++;
}
ans.clear();stak=0;tot=0;
for(int i=1;i<=n;i++)pre[i][0]=pre[i][1]=pre[i][2]=f[i][0]=f[i][1]=f[i][2]=head[i]=deg[i]=link[i][0]=link[i][1]=link[i][2]=0;
}
return 0;
}