2018年9月15日提高组模拟赛 T2 拆网线

版权声明:转载无所谓的。。。 https://blog.csdn.net/xuxiayang/article/details/82713782

大意

给定一张 n 个点, n 1 条边的无向联通图,现要在图中至少有一个由 m 个点组成的联通分量中的点数必须不小于2的情况下,割去尽量多的边。


思路

树形 d p

一条边可以用两只企鹅站,这样的一条点对,越多越好。
如果是 a n s 对点, 2 a n s k ,那么只需要 ( k + 1 ) 2 条边。
否则,需要 a n s + ( k 2 a n s ) 条边。
现在问题就转为求这样的点对有多少。

g [ x ] 表示以 i 为根的子树中能够两两配对的最大点数,不包含节点 i

f [ x ] 表示以 i 为根的子树中能够两两配对的最大点数,包含节点 i

得到方程

g [ x ] = y y x m a x { g [ y ] , f [ y ] }

f [ x ] = m a x { y y x f [ y ] f [ y ] + g [ y ] + 1 }


代码

#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;int n,m,t,f[100001],g[100001],ans;
vector<int>son[100001];
bool vis[100001];
inline int read()//输入优化
{
    int f=0,d=1;char c;
    while(c=getchar(),c<48||c>57)if(c=='-')d=-1;f=(f<<3)+(f<<1)+c-48;
    while(c=getchar(),c>47&&c<58)f=(f<<3)+(f<<1)+c-48;
    return d*f;
}
inline void write(register int x){if(x>9)write(x/10);putchar(x%10+48);return;}//输出优化
inline void dfs(register int x,register int fa)//树形dp
{
    if(vis[x]) return;
    vis[x]=true;//记得标记
    f[x]=0;g[x]=0;//初始化
    int sum=0;
    for(register int i=0;i<son[x].size();i++)
    {
        int y=son[x][i];
        if(y==fa) continue;
        dfs(y,x);
        g[x]+=max(f[y],g[y]);//动态转移
        sum+=f[y];//计算cgm f[y]
    }

    for(register int i=0;i<son[x].size();i++) 
    {
        int y=son[x][i];
        if(y==fa) continue;
        f[x]=max(f[x],sum-f[y]+g[y]+1);//动态转移
    }
    return;
}
signed main()
{
    t=read();
    while(t--)
    {
        n=read();m=read();
        for(register int i=0;i<=n;i++)son[i].clear();
        memset(vis,0,sizeof(vis));//初始化
        for(register int i=1,u;i<n;i++) 
        son[u=read()].push_back(i+1),son[i+1].push_back(u);//建边
        dfs(1,-1);
        ans=max(f[1],g[1]);//得出点对
        if((ans<<1)>=m) write((m+1)>>1),putchar(10);
        else write(ans+m-(ans<<1)),putchar(10);//输出
    }
}

猜你喜欢

转载自blog.csdn.net/xuxiayang/article/details/82713782
今日推荐