原题链接: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;
}