题目大意
给你一棵有
n
n
n 个节点的树,现在有一枚硬币在1号节点上。有两名玩家A、B,按照以下顺序操作:
a. A选择一个节点并标记它
b. B将硬币移动到一个与硬币当前所在的节点相邻的未被标记的节点
c.硬币原来所在节点被标记
A玩家只知道硬币一开始在在1号节点上,但他希望无论B如何操作硬币最多移动k次,问是否能完成。(输出‘DA’或‘NE’)
数据范围:
1
≤
k
≤
n
≤
400
1≤k≤n≤400
1≤k≤n≤400
分析
这道题有许多人想了各种贪心策略,但是出题人都卡了……
首先我们可以记节点深度为
d
e
p
(
d
e
p
[
1
]
=
0
)
dep (dep[1]=0)
dep(dep[1]=0)
那么对于
d
e
p
[
u
]
>
k
dep[u]>k
dep[u]>k 的节点我们其实是可以砍了的,因为硬币最多只会向下移动k次
然后假设现在是第i次操作,那么我们在
d
e
p
=
i
dep=i
dep=i 中选一个堵住是合理的,当
d
e
p
<
i
dep<i
dep<i时我们是堵不住的(我们并不知道硬币怎么走的);当
d
e
p
>
i
dep>i
dep>i 时,显然应该堵
d
e
p
=
i
dep=i
dep=i(可能在分岔口堵更多)
对于A而言,某一位置的硬币所在子树中图2是优于图1的,
可以理解为本来在分岔口堵一个变为还要在i+1堵一次。
为了方便描述,我们记
d
e
p
=
k
dep=k
dep=k 的节点为
l
e
a
f
leaf
leaf,
c
n
t
[
u
]
cnt[u]
cnt[u]表示子树内叶节点个数
那么我们发现,我们堵
c
n
t
[
u
]
cnt[u]
cnt[u] 更大的节点是利于A获胜的,所以我们要构造一棵树来使整体的
c
n
t
cnt
cnt都很小
构造的图如下:
那么我们发现叶节点总数为
⌊
n
/
k
⌋
\lfloor n/k\rfloor
⌊n/k⌋,我们可以堵的次数是k,而当floor(n/k) ≤k时我们就可以堵完,一次堵一个,我们想得到
n
≤
k
∗
k
n≤k*k
n≤k∗k,发现变形有点问题。那我们假设
n
≤
k
n≤k
n≤k 我们可以得到
⌊
n
/
k
⌋
\lfloor n/k \rfloor
⌊n/k⌋ ≤n≤k。即当
n
≤
k
∗
k
n≤k*k
n≤k∗k时是存在必胜策略的。
(这里我和题解有些出入,有兴趣的同学可以再看看题解证明)
那么我们只需要处理
k
2
<
n
k^2<n
k2<n 的情况了
我们发现
n
≤
400
n≤400
n≤400,那么
k
≤
19
k≤19
k≤19 ,我们就可以考虑一下Dp了。
我们将叶节点从左至右编号
l
e
a
f
[
i
]
leaf[i]
leaf[i],总数为tot。
我们定义f[i][S]:是否能用深度集合为S的节点将前i个叶节点被堵住。
注意,这里深度集合指的是S的第i位表示dep=i是否有节点被堵住
(因为同一深度节点只能被堵一次)
我们还可以用两个数组L[u],R[u]来记录u字数内包含的叶节点区间
(
l
e
a
f
[
L
[
u
]
]
,
l
e
a
f
[
R
[
u
]
]
]
(leaf[L[u]],leaf[R[u]]]
(leaf[L[u]],leaf[R[u]]].
我们再用
H
[
i
]
[
t
]
H[i][t]
H[i][t] 记录
R
[
u
]
R[u]
R[u] 均为i的节点编号u,便于转移
那么转台转移方程就是:
f
[
i
]
[
S
]
∣
=
f
[
L
[
H
[
i
]
[
t
]
]
]
[
S
−
(
1
<
<
(
d
e
p
[
H
[
i
]
[
t
]
]
−
1
)
)
]
f[i][S]|=f[L[H[i][t]]][S-(1<<(dep[H[i][t]]-1))]
f[i][S]∣=f[L[H[i][t]]][S−(1<<(dep[H[i][t]]−1))]
初始化
f
[
0
]
[
0
]
=
1
f[0][0]=1
f[0][0]=1
可以具体看实现
最后判断
f
[
t
o
t
]
[
j
]
(
1
<
=
j
<
(
1
<
<
k
)
)
f[tot][j](1<=j<(1<<k))
f[tot][j](1<=j<(1<<k)) 即可。
代码
#include<set>
#include<map>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<bitset>
#include<vector>
#include<climits>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define LL long long
int read(){
int f=1,x=0;char c=getchar();
while(c<'0'||'9'<c){if(c=='-')f=-1;c=getchar();}
while('0'<=c&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
return f*x;
}
#define MAXK 20
#define MAXN 400
#define INF 0x3f3f3f3f
vector<int> G[MAXN+5];
inline void Addedge(int u,int v){
G[u].push_back(v),G[v].push_back(u);
return ;
}
int n,k,cnt,fa[MAXN+5],dep[MAXN+5],L[MAXN+5],R[MAXN+5];
void DFS(int u){
dep[u]=dep[fa[u]]+1;
if(dep[u]==k){
L[u]=cnt++,R[u]=cnt;
return ;
}
L[u]=cnt;
for(int i=0;i<int(G[u].size());i++){
int v=G[u][i];
if(v==fa[u]) continue;
fa[v]=u,DFS(v);
}
R[u]=cnt;
return ;
}
vector<int> H[MAXN+5];
bool f[MAXN+5][1<<MAXK];
bool Dp(){//f[i][S]:前i个叶子用深度点集为S是否能阻挡
f[0][0]=1;
for(int i=1;i<=n;i++)
if(dep[i])
H[R[i]].push_back(i);
for(int i=1;i<=cnt;i++)
for(int t=0;t<int(H[i].size());t++)
for(int S=0;S<(1<<k);S++)
if(S&(1<<(dep[H[i][t]]-1)))
f[i][S]|=f[L[H[i][t]]][S^(1<<(dep[H[i][t]]-1))];
for(int S=0;S<(1<<k);S++)
if(f[cnt][S])
return 1;
return 0;
}
int main(){
//freopen("burza.in","r",stdin);
//freopen("burza.out","w",stdout);
n=read(),k=read();
if(n<=k*k){
puts("DA");
return 0;
}
for(int i=1;i<n;i++){
int u=read(),v=read();
Addedge(u,v);
}
dep[0]=-1;
DFS(1);
if(Dp()) puts("DA");
else puts("NE");
return 0;
}