洛谷 P6499
Posted ET2006
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了洛谷 P6499相关的知识,希望对你有一定的参考价值。
一道挺有意思的思维题(?)
首先我们假设根节点深度为 \\(0\\),那么 Daniel 的目标显然就是堵住一些节点使得 Stjepan 不能移动到深度为 \\(k\\) 的节点,Stjepan 的目标就是将棋子移到深度为 \\(k\\) 的节点。我们还可以发现一个显然的性质,就是 Daniel 在第 \\(i\\) 步肯定会堵住深度为 \\(i\\) 的节点(如果还存在深度为 \\(i\\) 的节点没有堵住),因为如果堵住一个深度 \\(<i\\) 的节点那显然是无效的,而如果堵住深度 \\(>i\\) 的节点,我们完全可以将其移到它深度为 \\(i\\) 的祖先上,这样肯定比堵住原来的节点来得更优。
于是现在问题就转化为,你需要选择一个集合 \\(S\\subseteq T=\\{1,2,3,\\cdots,k\\}\\) 并堵住深度为 \\(x\\) 的点各一个(\\(x\\in S\\)),使得不存在深度为 \\(k\\) 的点满足从根节点到该点的路径都被堵住了。
这样看上去还是很不好做,不过注意到当 \\(k\\) 比较大的时候答案都是 DA
,事实上,对于 \\(k\\ge\\sqrt{n}\\) 答案必定是 DA
,感性理解(因为我也不会严谨证明,想了半天没想通/wul)可知最劣情况大概是根节点下面接了 \\(k\\) 条长度为 \\(k\\) 的链,那么此时你只需在第 \\(i\\) 条链上堵住长度为 \\(i\\) 的边即可,这样我们就将 \\(k\\) 的规模降到了 \\(19\\)。
注意到这道题的状态与集合有关,因此考虑状压 \\(dp\\),由于每次堵住一个点会使得一个子树内的点到根节点的路径受阻,因此考虑 DFS 序,将所有深度为 \\(k\\) 的点按照 DFS 序从小到大排成一列,那么堵住每个点后会使一段区间 \\([L_x,R_x]\\) 内的深度为 \\(k\\) 的点不可到达,因此可以设 \\(dp_{i,S}\\) 表示 \\(1\\sim i\\) 的点已经被堵住了,堵住的点的深度组成的集合为 \\(S\\) 是否合法,转移就枚举 \\(x\\) 满足 \\(L_x=i+1\\) 且 \\(dep_x\\notin S\\),然后用 \\(dp_{i,S}\\) 更新 \\(dp_{R_x,S\\cup\\{dep_x\\}}\\) 即可。
时间复杂度 \\(n2^k\\)。
const int MAXN=400;
int n,k,hd[MAXN+5],to[(MAXN<<1)+5],nxt[(MAXN<<1)+5],ec=0;
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
int dep[MAXN+5],L[MAXN+5],R[MAXN+5],lcnt=0;bool on[MAXN+5];
void dfs(int x,int f){
on[x]=1;
if(dep[x]>=k) return L[x]=lcnt,R[x]=++lcnt,void();
L[x]=lcnt;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;
dep[y]=dep[x]+1;dfs(y,x);
} R[x]=lcnt;
}
vector<pii> idl[MAXN+5];
bool dp[MAXN+5][1048577];
int main(){
scanf("%d%d",&n,&k);if(k*k>=n) return printf("DA\\n"),0;
for(int i=1,u,v;i<n;adde(u,v),adde(v,u),i++) scanf("%d%d",&u,&v);
dfs(1,0);dp[0][0]=1;
for(int i=1;i<=n;i++) if(on[i]&&(i^1)) idl[L[i]].pb(mp(R[i],dep[i]));
for(int i=0;i<lcnt;i++) for(int j=0;j<(1<<k);j++)
for(pii p:idl[i]) if(~j>>(p.se-1)&1) dp[p.fi][j|(1<<p.se-1)]|=dp[i][j];
bool ret=0;for(int i=0;i<(1<<k);i++) ret|=dp[lcnt][i];
printf("%s\\n",ret?"DA":"NE");
return 0;
}
以上是关于洛谷 P6499的主要内容,如果未能解决你的问题,请参考以下文章