HDU 5927 Auxiliary Set (树形DP+思维)

题意:给一棵有根树,和m个“不重要”的点,但如果一个点是两个重要的点的最近公共祖先,它会变成重要的点。

思考:关键就是要知道每个不重要的点是否是某两个重要的点的lca;这个判断可以通过两次树dp来实现,第一次dp出每个节点有多少棵子树。

不难发现,只要节点node的某棵子树中含有一个重要的点,则这棵子树中一定含有一个点可以作为形成node为lca的其中一个点。(自己随便画一下很容易证明)

所以对给的m个点参照原图中的祖先关系新建一个图,即两个不重要的点a,b,如果a是b的祖先就连一条a指向b的边,否则不连(显然新图可能是不连通的)。

再在这个图上dp一次,求出每个节点有多少棵不含任何重要点的子树,用之前的一减,如果结果<2,说明这个点无法变成重要点,否则可以。


&&吐槽&& 

建新图的时候,一开始我用还是head数组邻接表,结果疯狂TLE!想了想,虽然说了每个case的m总数不超过1e5,但是最坏要清空q次长度为n的head数组,这个数远大于1e5,不如用vector,虽然单次清空慢点,但最多清空不超过1e5次。MD,改了就过了


【代码】

/* ***********************************************
Author        :angon

************************************************ */
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <stack>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <math.h>
#include <stdlib.h>
#include <time.h>
using namespace std;
#define showtime fprintf(stderr,"time = %.15f\n",clock() / (double)CLOCKS_PER_SEC)
#define lld %I64d
#define REP(i,k,n) for(int i=k;i<n;i++)
#define REPP(i,k,n) for(int i=k;i<=n;i++)
#define scan(d) scanf("%d",&d)
#define scanl(d) scanf("%I64d",&d)
#define scann(n,m) scanf("%d%d",&n,&m)
#define scannl(n,m) scanf("%I64d%I64d",&n,&m)
#define mst(a,k)  memset(a,k,sizeof(a))
#define LL long long
#define N 100100
#define mod 1000000007
inline int read(){int s=0;char ch=getchar();for(; ch<'0'||ch>'9'; ch=getchar());for(; ch>='0'&&ch<='9'; ch=getchar())s=s*10+ch-'0';return s;}

struct Edge
{
    int v,next;
}edge[N*2];
int head[N],tot;
void addedge(int u,int v)
{
    edge[tot].v = v;
    edge[tot].next = head[u];
    head[u] = tot++;
}
int sum[N],fa[N];
void get_sonnum(int u,int pre)  
{
    fa[u] = pre;
    sum[u] = 0;
    for(int i=head[u];~i;i=edge[i].next)
    {
        int v = edge[i].v;
        if(v==pre) continue;
        get_sonnum(v,u);
        sum[u]++;
    }
}

map<int,int>vis;
int ans;
vector<int>G[N];
int dfs(int u)
{
    if(vis[u] != -1) return vis[u];
    int son_tree = 0;
    int num = 0;
    for(int i=0;i<G[u].size();i++)
    {
        int v = G[u][i];
        if( dfs(v) >= 1 ) num++;
        son_tree++;
    }
    if(sum[u] - son_tree + num >=2)
    {
        ans++;
        return vis[u] = 2;
    }
    if(sum[u] - son_tree + num >=1)
    {
        return vis[u] = 1;
    }
    return vis[u] = -2;
}

int imp[N];
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int t,cas = 1;
    scan(t);
    while(t--)
    {
        int n,q,m;
        n=read();
        q=read();
        mst(head,-1); tot=0;
        REP(i,1,n)
        {
            int u,v;
            u=read();
            v=read();
            addedge(u,v);
            addedge(v,u);
        }
        get_sonnum(1,0);
        printf("Case #%d:\n",cas++);
        while(q--)
        {
            scan(m);
            vis.clear();
            REP(i,0,m)
            {
                imp[i] = read();
                vis[imp[i]] = -1;
                G[imp[i]].clear();
            }
            REP(i,0,m)
                if(vis[fa[imp[i]]] == -1)
                    G[fa[imp[i]]].push_back(imp[i]);
            ans = 0;
            REP(i,0,m)
            {
                if(vis[imp[i]] == -1)
                {
                     dfs(imp[i]);
                }
            }
            printf("%d\n",n-m+ans);
        }
    }


    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值