题目
(围住神经猫树上博弈版)
分析
注意你不知道对手如何移动,只知道树的样子,所以你不能根据他走的地方来堵结点,而题目的意思是问是否有必胜策略,即按照你设计的堵法,对手无论如何都移动不了 K K K次(博弈)。
显然,可以把深度超过
K
K
K的点先砍掉,当然,深度不到
K
K
K的枝叶也要砍掉。
发现,每一层(即同一深度)你只用堵一个结点,因为对手只能往下走,不能往上。
如果叶结点的数量不大于
K
K
K,有必胜策略,即堵住所有叶节点。
在这种必胜策略下,
N
N
N最多是多少?是
K
2
+
1
K^2+1
K2+1,即下图这种情况:
这意味着当
N
≤
K
2
+
1
N\leq K^2+1
N≤K2+1时,一定有堵完叶子结点的必胜策略。
于是我们只需要处理 K ≤ N K\leq\sqrt N K≤N的情况了,这时 K K K的数据范围是 K ≤ 20 K\leq 20 K≤20,状压DP直接来。
下面提到的叶结点从左至右的顺序就是dfs
中访问到的顺序。
集合
S
S
S表示每层堵不堵(前面已说到每层只用堵一个),
d
p
[
i
]
[
S
]
dp[i][S]
dp[i][S]表示在
S
S
S的情况下,能否恰好堵住前
i
i
i个叶结点。由于是恰好,所以
i
i
i一定是堵了的某个结点
u
u
u的最右边的叶结点,如:
设
l
e
f
t
[
u
]
left[u]
left[u]表示
u
u
u的最左端儿子在叶子中的编号,
d
e
p
[
u
]
dep[u]
dep[u]是
u
u
u的深度,枚举每一个
u
u
u,则有转移方程:
d
p
[
i
]
[
S
]
∣
=
d
p
[
l
e
f
t
[
u
]
−
1
]
[
S
−
(
1
<
<
d
e
p
[
u
]
)
]
dp[i][S]\ |=dp[left[u]-1][S-(1<<dep[u])]
dp[i][S] ∣=dp[left[u]−1][S−(1<<dep[u])]
代码
用vector
的
r
e
v
[
i
]
rev[i]
rev[i]存最右边叶结点为
i
i
i的所有结点即可。
#include<cmath>
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*f;
}
#define MAXK 20
#define MAXN 400
int N,K;
int G[MAXN+5][MAXN+5],fa[MAXN+5],dep[MAXN+5];
vector<int> Rev[MAXN+5];
int Leaves[MAXN+5],tot;
int Left[MAXN+5],Right[MAXN+5];
bool dp[MAXN+5][(1<<MAXK)+5];
bool Cut(int u,int f,int d){
int cnt=0;
for(int v=1;v<=N;v++)
if(v!=f&&G[u][v]){
if(d==K)
G[u][v]=G[v][u]=0;
else if(Cut(v,u,d+1))
G[u][v]=G[v][u]=0;
cnt+=G[u][v];
}
return !cnt&&d<K;
}
void Get(int u,int f,int d){
fa[u]=f,dep[u]=d;
if(dep[u]==K)
Leaves[++tot]=u;
for(int i=1;i<=N;i++)
if(G[u][i]&&i!=f)
Get(i,u,d+1);
}
int main(){
freopen("burza.in" ,"r", stdin);
freopen("burza.out","w",stdout);
N=read(),K=read();
if(K*K>=N)
return puts("DA"),0;//直接必胜
for(int i=1;i<=N-1;i++){
int u=read(),v=read();
G[u][v]=G[v][u]=1;
}
Cut(1,-1,0);
Get(1,-1,0);
//要分开写,我合在一起写有问题
for(int i=1;i<=tot;i++){
int u=Leaves[i];
while(u!=-1&&!Left[u])
Left[u]=i,u=fa[u];
}
for(int i=tot;i>=1;i--){
int u=Leaves[i];
while(u!=-1&&!Right[u])
Right[u]=i,Rev[i].push_back(u),u=fa[u];
}
//rihgt和left类似,但在后面没有用
dp[0][0]=1;
for(int i=1;i<=tot;i++)
for(int S=2;S<=(1<<(K+1))-1;S+=2)
for(int k=0;k<int(Rev[i].size());k++){
int anc=Rev[i][k];
if((S>>dep[anc])&1)
dp[i][S]|=dp[Left[anc]-1][S^(1<<dep[anc])];
}
for(int S=0;S<=(1<<(K+1))-1;S++)
if(dp[tot][S])
return puts("DA"),0;
puts("NE");
}