hdu 5156 离线的最近公共祖先+手写读入

这道题就是标记每个点对其所在子树的颜色增加的贡献值,如果u和v颜色相同,那么其中一个在此公共祖先l到根的路径贡献要-1,所以在l处先减去1,相当于抛弃这一对点中前一个点对颜色数增加的贡献,再用第二个和后面的取最近公共祖先,依次类推.最终再将将子节点的颜色数加在一起即可(重复已经事先减去),可以看做当下每个节点都能贡献1

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#define MAXN 100007
#define MAX 1500007

using namespace std;

int fa[MAXN];

int find ( int x )
{
    return fa[x]==x?x:fa[x]=find(fa[x]);
}

struct 
{
    int v,next;
}e[2][MAX];

void scan ( int&x  )
{
    char c;
    x = 0;
    c = getchar ( );
    while ( c < '0' || c >'9' ) c = getchar ();
    while ( c >= '0' && c <= '9' )
    {
        x = x * 10 + c - 48;
        c = getchar( );
    }
}

int cc = 0;
int head[2][MAXN];
int num[MAXN];
vector<int> a[MAXN];
vector<int> b[MAX];

void add ( int flag , int u , int v )
{
     e[flag][cc].v = v;
     e[flag][cc].next = head[flag][u];
     head[flag][u] = cc++;
}

void lca ( int u = 1 , int p = -1 )
{
    for ( int i = head[0][u] ; i != -1 ; i= e[0][i].next )
    {
        int v = e[0][i].v;
        if ( v == p ) continue;
        lca ( v , u  );
        fa[v] = u;
    }
    for ( int i = head[1][u] ; i != -1 ; i = e[1][i].next )
    {
        int v = e[1][i].v;
        num[find(v)]--;
    }
}

void dfs ( int u = 1 , int p = -1 )
{
    int len = a[u].size ( );
    for ( int i = 0 ; i < len ; i++ )
        b[a[u][i]].push_back ( u );
    for ( int i = head[0][u] ; i != -1 ; i = e[0][i].next )
    {
        int v = e[0][i].v;
        if ( v == p ) continue;
        dfs ( v , u );
    }
} 

void calc ( int u = 1 , int p = 0 )
{
    for ( int i = head[0][u] ; i != -1 ; i = e[0][i].next )
    {
        int v = e[0][i].v;
        if ( v == p ) continue;
        calc ( v , u );
    } 
    num[p] += num[u];
}

int n,m;

int main ( )
{
    int u,v;
    while ( ~scanf ( "%d%d" , &n , &m ) )
    {
        cc = 0;
        for ( int i = 1 ; i <= n ; i ++ )
            fa[i] = i;
        memset ( head , -1 , sizeof ( head ) );
        memset ( num , 0 , sizeof ( num ) );
        for ( int i = 1 ; i < n ; i++ )
        {
            scan ( u );
            scan ( v );
            add ( 0 , u , v );
            add ( 0 , v , u );
        }
        cc = 0;
        int up = 0;
        for ( int i = 0 ; i < m ; i++ )
        {
            scan ( u );
            scan ( v );
            a[u].push_back ( v );
            up = max ( v , up );
        }
        dfs ( );
        for ( int i = 1 ; i <= up ; i++ )
        {
            int len = b[i].size();
            if ( len == 0 ) continue;
            else if ( len == 1 ) 
                num[b[i][0]]++ , b[i].clear();
            else 
            {
                int pre = b[i][0];
                num[b[i][0]]++;
                for ( int j = 1 ; j < len ; j ++ )
                {
                    int v = b[i][j];
                    add ( 1 , v , pre );
                    num[v]++;
                    pre = v;
                }
               b[i].clear(); 
            }
        }
        lca ( );
        calc ( );
        for ( int i = 1 ; i <= n ; i++ ) a[i].clear();
        printf ( "%d" , num[1] );
        for ( int i = 2 ; i <= n ; i++ )
            printf ( " %d" , num[i] );
        printf ( "\n" );
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值