[loj2546][JSOI2018]潜入行动(树形DP)
题目描述
外星人又双叒叕要攻打地球了,外星母舰已经向地球航行!这一次,JYY 已经联系好了黄金舰队,打算联合所有 JSOIer 抵御外星人的进攻。
在黄金舰队就位之前,JYY 打算事先了解外星人的进攻计划。现在,携带了监听设备的特工已经秘密潜入了外星人的母舰,准备对外星人的通信实施监听。
外星人的母舰可以看成是一棵 n 个节点、 n-1 条边的无向树,树上的节点用 1,2,\cdots,n1,2,⋯,n 编号。JYY 的特工已经装备了隐形模块,可以在外星人母舰中不受限制地活动,可以神不知鬼不觉地在节点上安装监听设备。
如果在节点 uu 上安装监听设备,则 JYY 能够监听与 uu 直接相邻所有的节点的通信。换言之,如果在节点 uu 安装监听设备,则对于树中每一条边 (u,v),节点 vv 都会被监听。特别注意放置在节点 uu 的监听设备并不监听 uu 本身的通信,这是 JYY 特别为了防止外星人察觉部署的战术。
JYY 的特工一共携带了 kk个监听设备,现在 JYY 想知道,有多少种不同的放置监听设备的方法,能够使得母舰上所有节点的通信都被监听?为了避免浪费,每个节点至多只能安装一个监听设备,且监听设备必须被用完。
输入格式
输入第一行包含两个整数 n,kn,k ,表示母舰节点的数量 nn 和监听设备的数量 kk 。 接下来 n-1n−1 行,每行两个整数 u,vu,v (1\le u,v\le n)(1≤u,v≤n),表示树中的一条边。
输出格式
输出一行,表示满足条件的方案数。因为答案可能很大,你只需要输出答案 \(\text{mod 1,000,000,007}\)的余数即可。
显然是树形DP
但是推状态转移方程很想死啊!
\(首先说一下DP[i][j][0/1][0/1]表示在树上的i点,一共放了j个监听设备,点i是否被覆盖,点i上是否有监听设备时,监听覆盖完点i的子树(不包括i)有多少种方案\)。
\(显然我们在dfs中更新时,是从儿子更新父亲,所以上面指的覆盖是指儿子对父亲的覆盖。还有我们需要设另一个数组来DP,设那个数组为dp1\)
接下来就是恶心的四种情况讨论了。
首先,如果这个点更新后是DP[u][j][0][0]。
因为u没有被覆盖,所以son上一定没有监听设备,又因为需要完全覆盖,u不可能对son进行覆盖,所以son一定要被覆盖。
dp[u][i+j][0][0]=(dp[u][i+j][0][0]+(dp1[i][0][0]*1ll*dp[v][j][1][0])%mod)%mod;
如果是DP[u][j][0][1]。
因为u没有被覆盖,所以son上一定没有监听设备,又因为u上有监听设备,所以son有没有被覆盖都行。
dp[u][i+j][0][1]=(dp[u][i+j][0][1]+(dp1[i][0][1]*1ll*(dp[v][j][1][0]+dp[v][j][0][0])%mod)%mod)%mod;
如果是DP[u][j][1][0]。
因为u被覆盖了,所以u的更新之前的状态有可能没有被覆盖,或者被覆盖了,所以dp可以由两种更新前的状态(0,0)或(1,0)更新。
1.由(0,0)更新过来
这样的话,因为最终状态要求被覆盖,所以son上必须要有监听设备,又因为u不能对son进行覆盖,所以son一定要被覆盖。
2.由(1,0)更新过来
同理,因为u之前已经被覆盖了,所以son上有没有监听设备都可以,又因为u不能对son进行覆盖,所以son一定要被覆盖。
dp[u][i+j][1][0]=(dp[u][i+j][1][0]+((dp1[i][0][0]*1ll*dp[v][j][1][1])%mod+0ll+(dp1[i][1][0]*1ll*(dp[v][j][1][0]+dp[v][j][1][1])%mod)%mod)%mod)%mod;
如果是DP[u][j][1][1]。
像上种情况一样,这种的dp也可以又两种更新前的状态(0,1)或(1,1)
1.由(0,1)更新过来
因为u上有监听设备,所以son上有没有被覆盖都可以,又因为u没有被覆盖(状态上要求被覆盖),所以son上有监听设备。
2.由(1,1)更新过来
同理,不过这次u已经被覆盖了,所以son上有没有监听设备都可以。
dp[u][i+j][1][1]=(dp[u][i+j][1][1]+(dp1[i][0][1]*1ll*(dp[v][j][1][1]+dp[v][j][0][1])%mod)%mod+0ll+(dp1[i][1][1]*1ll*((((dp[v][j][1][0]+dp[v][j][1][1])%mod+dp[v][j][0][0])%mod+dp[v][j][0][1])%mod)%mod)%mod)%mod;
综上,四种情况的分类讨论已经完毕,程序主体也写出来了。
两个初始化:
dp[u][0][0][0]=dp[u][1][0][1]=1;
#include<bits/stdc++.h>
#define mod 1000000007
#define N 100010
#define M 110
using namespace std;
int n,m,to[N<<1],nxt[N<<1],head[N],cnt,size[N],x,y,dp[N][M][2][2],dp1[M][2][2];//哪一个点,子树放了几个,u是否覆盖,u是否放
void adde(int x,int y)
{
to[++cnt]=y;
nxt[cnt]=head[x];
head[x]=cnt;
}
void dfs(int u,int fa)
{
size[u]=1;
dp[u][0][0][0]=dp[u][1][0][1]=1;
for(int k=head[u];k;k=nxt[k])
{
int v=to[k];
if(v!=fa)
{
dfs(v,u);
for(int i=0;i<=m;i++)
{
dp1[i][0][0]=dp[u][i][0][0];
dp[u][i][0][0]=0;
dp1[i][1][0]=dp[u][i][1][0];
dp[u][i][1][0]=0;
dp1[i][0][1]=dp[u][i][0][1];
dp[u][i][0][1]=0;
dp1[i][1][1]=dp[u][i][1][1];
dp[u][i][1][1]=0;
}
for(int i=0;i<=min(size[u],m);i++)
{
for(int j=0;j<=min(size[v],m-i);j++)
{
dp[u][i+j][0][0]=(dp[u][i+j][0][0]+(dp1[i][0][0]*1ll*dp[v][j][1][0])%mod)%mod;
dp[u][i+j][0][1]=(dp[u][i+j][0][1]+(dp1[i][0][1]*1ll*(dp[v][j][1][0]+dp[v][j][0][0])%mod)%mod)%mod;
dp[u][i+j][1][0]=(dp[u][i+j][1][0]+((dp1[i][0][0]*1ll*dp[v][j][1][1])%mod+0ll+(dp1[i][1][0]*1ll*(dp[v][j][1][0]+dp[v][j][1][1])%mod)%mod)%mod)%mod;
dp[u][i+j][1][1]=(dp[u][i+j][1][1]+(dp1[i][0][1]*1ll*(dp[v][j][1][1]+dp[v][j][0][1])%mod)%mod+0ll+(dp1[i][1][1]*1ll*((((dp[v][j][1][0]+dp[v][j][1][1])%mod+dp[v][j][0][0])%mod+dp[v][j][0][1])%mod)%mod)%mod)%mod;
}
}
size[u]+=size[v];
}
}
}
int main()
{
// freopen("1.txt","r",stdin);
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
adde(x,y);
adde(y,x);
}
dfs(1,-1);
printf("%d\n",(dp[1][m][1][0]+dp[1][m][1][1])%mod);
return 0;
}