2017 ACM/ICPC Asia Regional Shenyang Online Ping Ping Ping 树链剖分+树状数组

原题链接:http://acm.hdu.edu.cn/showproblem.php?pid=6203

题目大意:给出n+1个节点的树( 3<n<10^4),并给出m对点(m<=50000),要求这m对点的最短链上至少有一个点背标记,问最少标记几个点。

先让我哭一会QAQ,昨天下午后半场一直在死磕这题,想了网络流,费用流,差分约束,树形DP就是没想到贪心,最后随便写了一发后效性明显地树形DP,假装没有划水(我好菜。。。。

其实这题可以稍微简化一下,假设只有一条链的情形。就是给出一条链和m对起始点和终点,要求这m对点之间至少有一个点被标记。

从左端点最接近左边界的一对点开始考虑,显然这对点一定要被选中,其次如果直接选中右端点,就可以同时标记最多的链。于是就有了一个贪心算法,每次选中左端点最靠左边界的未被标记的点对,标记这对点中的右端点。

然后回到这道题。对于被标记的点,都肯定有一个性质:是某个点对的LCA。这个可以反证法证明,如果有一个被标记点不是任意一对点的LCA,则不标记这个点,换做标记这个点往根节点遇到的第一个LCA点显然不会使得某个链的标记消失。

接着,对于深度最大的一个LCA,这个LCA肯定是要被标记的,因为不存在其他LCA在这个点对的链路上,于是就有了一个贪心算法,每次标记深度最大的,且其所在链路未被标记的LCA。

怎么知道某一条链路是否被标记过呢?这就很好处理了,只要用树链剖分来处理某个点是否被标记,同时用树状数组查询前缀和,可以得知一段区间内是否有被标记过的点。

代码:

#include <bits/stdc++.h>

using namespace std;
inline void read(int &x){
    char ch;
    bool flag=false;
    for (ch=getchar();!isdigit(ch);ch=getchar())if (ch=='-') flag=true;
    for (x=0;isdigit(ch);x=x*10+ch-'0',ch=getchar());
    x=flag?-x:x;
}

inline void read(long long &x){
    char ch;
    bool flag=false;
    for (ch=getchar();!isdigit(ch);ch=getchar())if (ch=='-') flag=true;
    for (x=0;isdigit(ch);x=x*10+ch-'0',ch=getchar());
    x=flag?-x:x;
}


inline void write(int x){
    static const int maxlen=100;
    static char s[maxlen];
        if (x<0) {   putchar('-'); x=-x;}
    if(!x){ putchar('0'); return; }
    int len=0; for(;x;x/=10) s[len++]=x % 10+'0';
    for(int i=len-1;i>=0;--i) putchar(s[i]);
}

int const MAXN=11000;
int const MAXM=21000;
int const MAXQ=61000;

int n,m;
int pre[ MAXM ] , now[ MAXN ] ,son[ MAXM ],tot;

int pos[ MAXN ],cnt;
int taken[ MAXN ];


int siz[ MAXN ] , deep[ MAXN ];
int top[ MAXN ] , fa[ MAXN ];


void build(int a,int b){
pre[++tot]=now[a];
now[a]=tot;
son[tot]=b;
}


void get_size_deep_fa(int x){
siz[ x ] = 1;
for (int p=now[x];p;p=pre[p])
    if( son[p] != fa[ x ] )
        {
            deep[ son[p] ] = deep[ x ] + 1;
            fa[ son[p] ] = x;
            get_size_deep_fa( son[p]  );
            siz[ x ] += siz[ son[p] ];
        }
}

void get_pos_top(int x,int fa){
bool op=0;
int Max = 0;
int Maxi = 0 ;
++cnt;  pos[ x ] = cnt;
for (int p = now[ x ] ; p ; p=pre[p] )
    if ( son[p] != fa )
        if ( Max < siz[ son[p] ] )
            {
                Max = siz[ son[p] ];
                Maxi = son[p] ;
            }
if ( Maxi )
    {
        top[ Maxi ] = top[ x ];
        get_pos_top( Maxi , x );
    }
for (int p = now[ x ]; p ; p=pre[p] )
    if ( ( son[p]!=fa) && ( son[p]!=Maxi ) )
        {
            top[ son[p] ] = son[p];
            get_pos_top( son[p] , x );
        }
}


int lowbit( int x){
return x&(-x);
}

int get_taken(int x ){
int sum=0;
while( x )
    {
        sum+=taken[ x ];
        x-=lowbit( x );
    }
return sum;
}

void add(int x){
while ( x<=n  )
    {
        taken[ x ]++;
        x+=lowbit( x );
    }
}


struct Query{
    int u , v ,lca;

    void get_lca(){
    int x = u , y= v;
    while ( top[ x ] != top[ y ] )
        {
            if ( deep [ top[ x ] ] > deep [ top[ y ] ])
                swap ( x , y );
            y = fa [ top[ y ] ];
        }
    lca = ( deep [ x ] < deep [ y ] )?x:y;
    }

    bool exist(){
    int x = u , y= v;
    while ( top[ x ] != top[ y ] )
        {
            if ( deep [ top[ x ] ] > deep [ top[ y ] ])
                swap ( x , y );
            if ( get_taken( pos[ y ] ) -  get_taken( pos[ top[ y ] ]-1 ) != 0 )
                return 0;
            y = fa [ top[ y ] ];
        }
    if ( get_taken( pos[ x ] ) - get_taken( pos[ lca ]-1 ) !=0)
        return 0;
    if ( get_taken( pos[ y ] ) - get_taken( pos[ lca ]-1 ) !=0)
        return 0;
    return 1;
    }
}que[ MAXQ ];


bool cmp(Query A , Query B){
return deep[ A.lca ] > deep[ B.lca ];
}


int main(){
    while (scanf("%d",&n)!=EOF)
        {
            tot=0;
            memset(now,0,sizeof(now));
            n++;
            for (int i=1;i<n;i++)
                {
                    int a,b;
                    read(a); read(b);
                    a++; b++;
                    build( a , b );
                    build( b , a );
                }
            cnt=0;
            deep[1]=0;
            fa[1]=0;
            get_size_deep_fa( 1 );
            top[1]=1;
            get_pos_top( 1 , 0 );
            read(m);
            for (int i=1;i<=m;i++)
                {
                    read( que[i].u );
                    read( que[i].v );
                    que[i].u++; que[i].v++;
                    que[i].get_lca();
                }
            sort( que+1 , que+m+1 , cmp );
            memset( taken , 0 , sizeof( taken ) );
            for (int i=1;i<=m;i++)
                if (que[i].exist())
                    add( pos [ que[i].lca ] );
            printf("%d\n",get_taken( n ) );
        }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值