zoukankan      html  css  js  c++  java
  • HDU 5927 Auxiliary Set

    这是2016CCPC东北四省赛的F题。
    现场的做法有点繁琐,赛后和队友LSH讨论了一下,他提出了一个排序后$O(N)$的做法.


    题意

    每次询问给出一个unimportant node (以下简称u-node) 的集合 $S$.
    要求统计出$S$中有多少个节点是某两个不同的important node (以下简称i-node) 的LCA.
    (当然这是转化后的题意, 题目并不是这样问的)

    做法

    对u-node按逆DFS序排序.
    遍历$S$, 对每个u-node $u$, 维护$u$的满足如下条件的儿子$v$数目:

    子树$v$中全是u-node

    不妨将此数目记作$cnt[u]$.
    维护的方法是
    对某个 $u$, 若 cnt[u]==0--cnt[par[u]].
    $par[u]$表示 $u$ 的父亲.

    最后, 满足 $cnt[u] ge 2$ 的 $u$ 的数目即为所求.

    Implementation

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N=1e5+5;
    
    int L[N];
    
    int cnt[N];
    
    int tail;
    
    vector<int> g[N];
    int par[N];
    
    void dfs(int u, int f){
        L[u]=++tail;
        par[u]=f;
    
        for(auto v: g[u]){
            if(v!=f){
                ++cnt[u];
                dfs(v, u);
            }
        }
    }
    
    bool cmp(int x, int y){
        return L[x]>L[y];
    }
    
    
    vector<int> a;  // unimportant nodes
    
    int _cnt[N];
    // bool flag[N];
    
    int main(){
        int T, cas{};
        for(cin>>T; T--; ){
            int n, q;
            cin>>n>>q;
    
            for(int i=1; i<=n; i++){
                g[i].clear();
                cnt[i]=0;   // error-prone
            }
    
            for(int i=1; i<n; i++){
                int u, v;
                scanf("%d%d", &u, &v);
                g[u].push_back(v);
                g[v].push_back(u);
            }
    
            tail=0;
            dfs(1, 0);
    
            printf("Case #%d:
    ", ++cas);
    
            for(; q--; ){
                int m;
                cin>>m;
                a.clear();
                for(; m--; ){
                    int x;
                    scanf("%d", &x);
                    a.push_back(x);
                    // flag[x]=true;
                }
    
                sort(a.begin(), a.end(), cmp);
                for(auto x: a){
                    _cnt[x]=cnt[x];
                }
    
                for(auto x: a){
                    if(_cnt[x]==0){
                        --_cnt[par[x]];
                    }
                }
                int res=0;
                for(auto x: a){
                    res+=_cnt[x]>=2;
                    // _cnt[x]=cnt[x];
                }
                res += n-a.size();
                printf("%d
    ", res);
            }
        }
        return 0;
    }
    

    UPD
    ICPCCamp给出的题解上这题的做法是

    题意:给定一棵树,每次询问一个集合,问所有不在这个集合的点两两 LCA (可以相同)的并集大小。
    题解:实际上就是对于每个被删掉的点,check 一下是不是除了一个子树之外的点都被删掉了。那么对于每个被删掉的点的连通块,从最高点开始 dfs 就行。

    一开始并不能看懂 (太弱), 现在大概理解了.
    其实思路和上面是一样的, 最终的目的还是: 对每个被删掉的点 $u$, 统计出$u$有多少个儿子$v$的子树全被删了, 要统计这个量, 只要考虑每个被删掉的点的连通块就好了. 因为如果$u$的某个儿子$v$的子树全被删了, 那么这些被删掉的点一定也出现在$u$所在的连通块中.

    求每个连通块的最高点 (根) 和dfs的复杂度是$O(n)$的. 而我的做法处理单次询问的复杂度是$O(mlog m) + O(m)$, $m$是被删掉点的数目.
    并不用找一个删除点的连通块的最高点, 按通常DFS的写法写就可以了, 然而我的DFS()函数第一次却写跪了.

    void DFS(int u){
    	used[u]=true;
    	cnt[u]=1;
    	int c=0;
    	for(auto v: G[u]){
    		if(!used[v]) DFS(v), cnt[u]+=cnt[v];
    		// if(cnt[v]<size[v]) c++;
    		c+=cnt[v]==size[v];
    	}
    	ans+=c+1 < g[u].size()-(u!=1);
    }
    

    正确的写法:

    
    void DFS(int u){
    	used[u]=true;
    	cnt[u]=1;
    	int c=0;
    	for(auto v: G[u]){
    		if(!used[v]) DFS(v);
    		cnt[u]+=cnt[v];	// error-prone
    		// if(cnt[v]<size[v]) c++;
    		c+=cnt[v]==size[v];
    	}
    	ans+=c+1 < g[u].size()-(u!=1);
    }
    

    Implementation

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N=1e5+5;
    vector<int> g[N];
    vector<int> G[N];
    int a[N];
    
    bool used[N];
    
    int par[N], size[N];
    
    void dfs(int u, int f){
    	par[u]=f;
    	size[u]=1;
    	for(auto v: g[u]){
    		if(v!=f) dfs(v, u), size[u]+=size[v];
    	}
    }
    
    int cnt[N], ans;
    
    
    
    void DFS(int u){
    	used[u]=true;
    	cnt[u]=1;
    	int c=0;
    	for(auto v: G[u]){
    		if(!used[v]) DFS(v);
    		cnt[u]+=cnt[v];	// error-prone
    		// if(cnt[v]<size[v]) c++;
    		c+=cnt[v]==size[v];
    	}
    	ans+=c+1 < g[u].size()-(u!=1);
    }
    
    bool f[N];
    
    int main(){
    	int T, n, q, cs{};
    	for(cin>>T; T--; ){
    		printf("Case #%d:
    ", ++cs);
    		cin>>n>>q;
    		for(int i=1; i<=n; i++) g[i].clear();
    		for(int i=1; i<n; i++){
    			int u, v;
    			scanf("%d%d", &u, &v);
    			g[u].push_back(v);
    			g[v].push_back(u);
    		}
    		dfs(1, 0);
    		for(; q--; ){
    			int m;
    			scanf("%d", &m);
    			for(int i=0; i<m; i++){	
    				scanf("%d", a+i);
    				used[a[i]]=false;
    				G[a[i]].clear();
    				f[a[i]]=true;
    			}
    			for(int i=0; i<m; i++){
    				if(f[par[a[i]]])
    					G[par[a[i]]].push_back(a[i]);
    			}
    			ans=0;
    			for(int i=0; i<m; i++)
    				if(!used[a[i]]) DFS(a[i]);
    			for(int i=0; i<m; i++)
    				f[a[i]]=false;
    			printf("%d
    ", n-m+ans);
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    python练习册 0002随机生成验证
    女生应该找什么样的男生
    博弈论 尼姆博弈,人生第一道博弈纪念一下
    博弈论版版
    博弈论
    STL用法大全
    据说是Flord算法
    20190716-T3-奇袭
    20190716-T1-礼物
    20190716-被水题淹没
  • 原文地址:https://www.cnblogs.com/Patt/p/5969771.html
Copyright © 2011-2022 走看看