其他分享
首页 > 其他分享> > Infected Tree

Infected Tree

作者:互联网

题目:

Byteland is a beautiful land known because of its beautiful trees.

Misha has found a binary tree with nn vertices, numbered from 11 to nn. A binary tree is an acyclic connected bidirectional graph containing nn vertices and n−1n−1 edges. Each vertex has a degree at most 33, whereas the root is the vertex with the number 11 and it has a degree at most 22.

Unfortunately, the root got infected.

The following process happens nn times:

As Misha does not have much time to think, please tell him what is the maximum number of vertices he can save from the infection (note that deleted vertices are not counted as saved).

Input

There are several test cases in the input data. The first line contains a single integer tt (1≤t≤50001≤t≤5000) — the number of test cases. This is followed by the test cases description.

The first line of each test case contains one integer nn (2≤n≤3⋅1052≤n≤3⋅105) — the number of vertices of the tree.

The ii-th of the following n−1n−1 lines in the test case contains two positive integers uiui and vivi (1≤ui,vi≤n1≤ui,vi≤n), meaning that there exists an edge between them in the graph.

It is guaranteed that the graph is a binary tree rooted at 11. It is also guaranteed that the sum of nn over all test cases won't exceed 3⋅1053⋅105.

Output

For each test case, output the maximum number of vertices Misha can save.

Example input
4
2
1 2
4
1 2
2 3
2 4
7
1 2
1 5
2 3
2 4
5 6
5 7
15
1 2
2 3
3 4
4 5
4 6
3 7
2 8
1 9
9 10
9 11
10 12
10 13
11 14
11 15
output
0
2
2
10

大意:二叉树的根部被感染,每次都可以选择一个子节点切除这个子节点的所有边,这个节点的所有子节点即被拯救,问最多能拯救多少个子节点。

  我们不难发现,对于每个除了根节点的节点只有两个选择,即作为被选中切边的节点或者被拯救的节点,由于题目所求是拯救节点的最大值,这样根据状态求答案的情况我们很容易便可以想到dp,加上总体框是一个二叉树,那么自然便是树形dp了。

  想到树形dp之后,我们又该如何构造状态转移方程呢?首先,由于受到感染是一层一层传递的,那么我们就需要考虑每一次要选择这一层的哪一个节点。显然我们不能从上向下每次选择子节点最多的树,那我们便要考虑从下至上更新每一层的状态,这也是树形dp的基本思路,即从最下边一层逐步选择子节点最多的节点更新到父亲节点,这样最后f[1]便是我们所求的答案。那么对于这一题,我们要对每一个节点更新第一需要考虑的便是每个节点下子节点的数量。因此我们首先要dfs一次将其求出。

代码如下:

now是当前节点,father是其父节点,cnt保存的是now的子节点的数量。v[now]保存的是与now相连接的节点。

void dfs1(int now,int father){
    cnt[now]=1;//初始为1即自己,方便更新每一个节点的cnt
    for(int i=0;i<v[now].size();i++){
        int op=v[now][i];
        if(op==father) continue;//如果是父亲节点则跳过
        dfs1(op,now);//递归求子节点的子节点数量
        cnt[now]+=cnt[op];//更新答案
    }
}

至于为什么要将cnt[now]初始化为1,即倒数第二层可以更新加上最后一层的子节点,否则全都是0也加不了对吧,这样就可以一层一层累加起来了。

但是要注意的是,因为这里初始化了1,所以实际的子节点数量要减去1,即把自身减去了。

  预处理子节点的数量后,我们便要考虑当一个节点已经被感染我们能拯救的最大子节点数量

注:红色为已经被感染的节点,蓝色是其父节点,黄色是其子节点

第一种,只有一条边:

 

 

显而易见,这种情况下红色感染后没有能切除的黄色(即红色为最后一层),即没有子节点可以被拯救,那么f[now]便可以直接为0,

代码为:

if(v[now].size()==1&&now!=1) f[now]=0;

第二种,有两条边:

对于这种情况,如果红色已被感染,因为只有一个子节点,那么能拯救的便是黄色cnt的数量减去1(切除黄色的所有边),即:

即f【红】=cnt【黄】-1;

else if(v[now].size()==2&&now!=1){
        for(int i=0;i<v[now].size();i++){
            int op=v[now][i];
            if(op==father) continue;
            f[now]=cnt[op]-1;
        }
    }

 第三种:有三条边的情况:

这种情况下当红色被感染后,我们可以拯救一个黄色的全部子节点,假如我们拯救左边的黄色,那么可以拯救cnt【左黄】个子节点,而对于右边的黄色,下一轮肯定会被感染,那么我们可以保留的字数数量即为f【右黄】加起来便是总共保留的子节点数量,当然要选择两种情况的最大值。

int a=-1,b=-1;//a与b分别表示两个子节点
        for(int i=0;i<v[now].size();i++) {
            int op=v[now][i];
            if(op==father) continue;
            if(a==-1) a=op;
            else if(b==-1) b=op;
            dfs2(op,now);
        }
        f[now]=max(f[a]+cnt[b]-1,f[b]+cnt[a]-1);

 

到此为止,本题也就可以ac了,现在把ac代码贴出来:

#include<iostream>
#include<vector>
#include<algorithm>
#include<cstring>
using namespace std;

const int maxn=300010;
vector<int>v[maxn];
int cnt[maxn],t,n,f[maxn];

void dfs1(int now,int father){
    cnt[now]=1;//初始为1即自己,方便更新每一个节点的cnt
    for(int i=0;i<v[now].size();i++){
        int op=v[now][i];
        if(op==father) continue;//如果是父亲节点则跳过
        dfs1(op,now);//递归求子节点的子节点数量
        cnt[now]+=cnt[op];//更新答案
    }
}

void dfs2(int now,int father){
	if(v[now].size()==1&&now!=1) f[now]=0;
    else if(v[now].size()==2&&now!=1){
        for(int i=0;i<v[now].size();i++){
            int op=v[now][i];
            if(op==father) continue;
            f[now]=cnt[op]-1;
        }
    }
    else{
        int a=-1,b=-1;//a与b分别表示两个子节点
        for(int i=0;i<v[now].size();i++) {
            int op=v[now][i];
            if(op==father) continue;
            if(a==-1) a=op;
            else if(b==-1) b=op;
            dfs2(op,now);
        }
        f[now]=max(f[a]+cnt[b]-1,f[b]+cnt[a]-1);
    }
}

void solve() {
    cin>>n;
    for(int i=1;i<=n;i++) v[i].clear(),f[i]=0,cnt[i]=0;
    for(int i=1;i<n;i++){
        int a,b;
        cin>>a>>b;
        v[a].push_back(b);
        v[b].push_back(a);
    }
    if(n==1){
        cout<<0<<'\n';
        return ;
    }
    dfs1(1,-1);
    dfs2(1,-1);
    cout<<f[1]<<'\n';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>t;
    while(t--){
        solve();
    }
    return 0;
}

感谢各位大佬观摩!

标签:cnt,int,Tree,vertices,Infected,test,now,节点
来源: https://www.cnblogs.com/cbmango/p/16367217.html