【题解】P4516 [JSOI2018] 潜入行动
【题解】P4516 [JSOI2018] 潜入行动
比较常规但是有点思维含量的一道树形 DP。
题目链接
题意概述
给定一棵有 \(n\) 个点的树,现在要在一些点上安装最多 \(k\) 个监听设备,每个监听设备可以监听到与这个点直接连边的所有点,但不可以监听到它本身,现在要求每个点都被监听到,且每个点最多只能有一个监听设备,求有多少种合法的方案,答案对 \(1e9+7\) 取模。
题目分析
首先比较显然的是个树上背包。
那么按照树上背包的套路,我们定义 \(dp_{x,i}\) 表示的是以 \(x\) 为根的子树,安装了 \(i\) 个监听设备有多少种合法的方案。
但是发现直接这样定义并不好转移,必须要考虑 \(x\) 本身是否放了监听设备以及有没有被监听。
所以我们更改一下 \(dp\) 状态:定义 \(dp_{x,i,0/1,0/1}\) 表示的是以 \(x\) 为根的子树,安装了 \(i\) 个监听设备,\(x\) 是否安装了监听设备,\(x\) 是否被监听的方案数。
那么最后的答案就是 \(dp_{1,k,0,1}+dp_{1,k,1,1}\)。
树上背包的转移套路是考虑合并子树,即考虑 \(dp_{x,i+j}\) 怎么样由 \(dp_{x,i}\) 和 \(dp_{y,j}\) 转移而来,其中 \(y\) 是 \(x\) 的儿子。
对于这样比较复杂的状态,我们分别考虑以 \(x\) 为根的每个状态应该怎么转移。
-
对于 \(dp_{x,i+j,0,0}\):
\(x\) 没有被监听,说明 \(y\) 一定没有安装监听设备;\(x\) 没有安装设备,说明 \(y\) 一定需要被除 \(x\) 之外的其它点监听。
转移方程:
\[ dp_{x,i+j,0,0}=\sum dp_{x,i,0,0}\times dp_{y,j,0,1} \] -
对于 \(dp_{x,i+j,0,1}\):
\(x\) 没有安装设备,说明 \(y\) 一定被其它点监听;\(x\) 被监听了,有两种情况:
- \(x\) 在考虑 \(y\) 之前就已经被其它儿子监听,此时 \(y\) 之前安装不安装设备无所谓;
- \(x\) 在考虑 \(y\) 之前还未被其它儿子监听,所以它只能被 \(y\) 监听,即 \(y\) 必须安装设备。
转移方程:
\[ dp_{x,i+j,0,1}=\sum dp_{x,i,0,1} \times (dp_{y,i,0,1}+dp_{y,j,1,1})+dp_{x,i,0,0}\times dp_{y,j,1,1} \] -
对于 \(dp_{x,i+j,1,0}\):
\(x\) 安装设备了,那么 \(y\) 之前是否被监听无所谓,因为反正它至少会被 \(x\) 监听;\(x\) 没有被监听,那么 \(y\) 一定不能安装设备。
转移方程:
\[ dp_{x,i+j,1,0}=\sum dp_{x,i,1,0} \times (dp_{y,j,0,1}+dp_{y,j,0,0}) \] -
对于 \(dp_{x,i+j,1,1}\):
\(x\) 安装设备了,那么 \(y\) 之前是否被监听无所谓,因为反正它至少会被 \(x\) 监听;\(x\) 被监听了,有两种情况:
- \(x\) 在考虑 \(y\) 之前就已经被其它儿子监听,此时 \(y\) 之前安装不安装设备无所谓;
- \(x\) 在考虑 \(y\) 之前还未被其它儿子监听,所以她只能被 \(y\) 监听,即 \(y\) 必须安装设备。
考虑 \(x\) 在 \(y\) 之前是否被监听时,同时考虑 \(y\) 之前是否被监听,总转移方程如下:
\[ dp_{x,i+j,1,1}=\sum dp_{x,i,1,1}\times (dp_{y,i,1,1}+dp_{y,i,1,0}+dp_{y,i,0,1}+dp_{y,i,1,1})//x~ 之前就被监听时,y~ 的状态无所谓 \\+dp_{x,i,1,0}\times(dp_{y,i,1,1}+dp_{y,i,1,0})//x~之前没有被监听时,y~ 必须安装设备,但是否被监听无所谓 \]
这就是全部的转移。
易错点
- 开 long long 会 MLE,请使用 unsigned int;
- 该取模的地方要取模;
- 树上背包每次转移前,先将 \(dp_{x,i,0/1,0/1}\) 下的每一个状态都存一个临时数组下面只用临时数组转移,并将所有原 $dp_{x,i,0/1,0/1} $ 归零,防止之后的转移出现错乱。
代码实现
//luoguP4516
#include<iostream>
#include<cstdio>
#include<cstring>
#define int unsigned int
using namespace std;
const int maxn=1e5+10;
const int maxk=105;
const int mod=1e9+7;
int dp[maxn][maxk][2][2],tp[maxk][2][2];
int sz[maxn];
int n,k;
basic_string<int>edge[maxn];
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
void dfs(int x,int fa)
{
sz[x]=dp[x][0][0][0]=dp[x][1][1][0]=1;
for(int y:edge[x])
{
if(y==fa)continue;
dfs(y,x);
for(int i=0;i<=min(sz[x],k);i++)
{
tp[i][0][0]=dp[x][i][0][0];
tp[i][0][1]=dp[x][i][0][1];
tp[i][1][0]=dp[x][i][1][0];
tp[i][1][1]=dp[x][i][1][1];
dp[x][i][0][0]=0;
dp[x][i][0][1]=0;
dp[x][i][1][0]=0;
dp[x][i][1][1]=0;
}
for(int i=0;i<=min(sz[x],k);i++)
{
for(int j=0;j<=min(sz[y],k-i);j++)
{
(dp[x][i+j][0][0]+=1ll*tp[i][0][0]*dp[y][j][0][1]%mod)%=mod;
(dp[x][i+j][0][1]+=1ll*tp[i][0][0]*dp[y][j][1][1]%mod+1ll*tp[i][0][1]*(dp[y][j][0][1]+dp[y][j][1][1])%mod)%=mod;
(dp[x][i+j][1][0]+=1ll*tp[i][1][0]*(dp[y][j][0][0]+dp[y][j][0][1])%mod)%=mod;
(dp[x][i+j][1][1]+=1ll*tp[i][1][0]*(dp[y][j][1][0]+dp[y][j][1][1])%mod+1ll*tp[i][1][1]*(dp[y][j][0][0]+dp[y][j][0][1]+dp[y][j][1][0]+dp[y][j][1][1])%mod)%=mod;
}
}
sz[x]+=sz[y];
}
}
signed main()
{
n=read();k=read();
for(int i=1;i<n;i++)
{
int u,v;
u=read();v=read();
edge[u]+=v;
edge[v]+=u;
}
dfs(1,0);
cout<<1ll*(dp[1][k][0][1]+dp[1][k][1][1])%mod<<"\n";
return 0;
}