题解——将军令
*这道题我看到有人打了树形 DP *
我当时想,每种情况都要讨论,20+的DP方程,那位神仙是给某主播打赏了10万后气急败坏了吗?
有的时候,可以贪心的别莽着打DP啊
有道简化版:P2279 [HNOI2003]消防局的设立(树形DP or 贪心)
题目搬运
Luogu传送门:P3942 将军令
又想起了四月。
如果不是省选,大家大概不会这么轻易地分道扬镳吧? 只见一个又一个昔日的队友离开了机房。
凭君莫话封侯事,一将功成万骨枯。
梦里,小 F 成了一个给将军送密信的信使。
现在,有两封关乎国家生死的密信需要送到前线大将军帐下,路途凶险,时间紧迫。小 F 不因为自己的祸福而避趋之,勇敢地承担了这个任务。
不过,小 F 实在是太粗心了,他一不小心把两封密信中的一封给弄掉了。
小 F 偷偷打开了剩下的那封密信。他 发现一副十分详细的地图,以及几句批文——原来 这是战场周围的情报地图。他仔细看后发现,在这张地图上标记了 n 个从 1 到 n 标号的 驿站,n − 1 条长度为 1 里的小道,每条小道双向连接两个不同的驿站,并且驿站之间可以 通过小道两两可达。
小 F 仔细辨认着上面的批注,突然明白了丢失的信的内容了。原来,每个驿站都可以驻 扎一个小队,每个小队可以控制距离不超过 k 里的驿站。如果有驿站没被控制,就容易产 生危险——因此这种情况应该完全避免。而那封丢失的密信里,就装着朝廷数学重臣留下的 精妙的排布方案,也就是用了最少的小队来控制所有驿站。
小 F 知道,如果能计算出最优方案的话,也许他就能够将功赎过,免于死罪。他找到了 你,你能帮帮他吗? 当然,小 F 在等待你的支援的过程中,也许已经从图上观察出了一些可能会比较有用的 性质,他会通过一种特殊的方式告诉你。
解题思路
一些分析
1.结构是一棵树,就意味着不会出现环,这里满足无后效性。
2.DP是不大可能的,我们不可能浪费时间写出巨量的DP转移方程。
3.答案具有一定程度上的单调(图大致不变,不断增多点数,答案增加)
4.每一个点都必须处理(这可以是一句废话)
二次分析
1.如果我们把节点按照一定的顺序处理,从而使我们安排守卫的收益(覆盖的点更多),那么一定会有一种更优的情况。
2。如果对一棵树自顶向下处理,在优先考虑了顶部后,我们可能在根节点浪费更多的守卫。(因为每个守卫的控制距离一定,这样等效于降低了守卫的价值)。
解法
我们将所有根节点按照深度排序,优先处理深度较大的节点,如果这个点没有被守卫,则在它向上距离最远的地方布置守卫,同时更新标记。
扫描点,处理深度的时候用BFS是O(n)的,ssw02懒得写,就写了O(nlogn)的dfs+优先队列。
然后对每个安排守卫的节点进行染色dfs(不然会被菊花图卡掉2个点)
AC code
#include<bits/stdc++.h>
using namespace std ;
const int MAXN = 1e5+5 ;
inline int read(){
int s = 0 ; char g= getchar() ; while( g>'9'||g<'0')g=getchar() ;
while( g>='0'&&g<='9' )s=s*10+g-'0',g=getchar() ; return s ;
}
int dep[ MAXN ] , head[ MAXN*2 ] , to[ MAXN*2 ] , nex[ MAXN*2 ] , tot = 1 ;
int N , M , K , fa[ MAXN ] , ans = 0 ;
int vis[ MAXN ] ;
priority_queue< pair<int,int> >q ;
void add( int x , int y ){
to[ ++tot ] = y , nex[ tot ] = head[ x ] , head[ x ] = tot ;
}
void dfs( int u , int father ){//这里推荐写BFS , 少个log
fa[ u ] = father ;
q.push( make_pair(dep[u],u) ) ;
for( int i = head[ u ] ; i ; i = nex[ i ] ){
if( to[ i ] == father )continue ;
dep[ to[ i ] ] = dep[ u ]+ 1 ;
dfs( to[ i ] , u ) ;
}
}
void dfs2( int u , int dis , int kind ){
vis[ u ] = kind ;
for( int i = head[ u ] ; i ; i = nex[ i ] )
if( dis <= K-1 && vis[ to[ i ] ] != kind ){//染色,不然会被卡
vis[ to[i] ] = kind ; dfs2( to[ i ] , dis+1 , kind ) ;
}
}
int main(){
N = read() , K = read() ; int m1 , m2 = read();
for( int i = 2 ; i <= N ; ++i ){
m1 = read() , m2 = read() ; add( m2 , m1 ) , add( m1 , m2 ) ;
}
dfs( 1 , 0 ) ;
while( !q.empty() ){
int d = q.top().second ; q.pop();
if( !vis[d] ){
ans++ ;
int target = d ;
for( int i = 1 ; fa[ target ]&&i<=K ; ++i )target = fa[ target ] ;//找最远的安排守卫的点
vis[ target ] = target ;
dfs2( target , 0 , target ) ;
}
}
cout<<ans ;
}
树上的点覆盖问题大多都和将军令这道题比较相似,如果题意符合,就可以考虑从根节点向上的贪心策略。