[loj2546][JSOI2018]潜入行动(树形DP)

3 篇文章 0 订阅

题目描述
外星人又双叒叕要攻打地球了,外星母舰已经向地球航行!这一次,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),表示树中的一条边。

输出格式
输出一行,表示满足条件的方案数。因为答案可能很大,你只需要输出答案 mod 1,000,000,007 \text{mod 1,000,000,007} mod 1,000,000,007的余数即可。

显然是树形DP

但是推状态转移方程很想死啊!

首 先 说 一 下 D P [ i ] [ j ] [ 0 / 1 ] [ 0 / 1 ] 表 示 在 树 上 的 i 点 , 一 共 放 了 j 个 监 听 设 备 , 点 i 是 否 被 覆 盖 , 点 i 上 是 否 有 监 听 设 备 时 , 监 听 覆 盖 完 点 i 的 子 树 ( 不 包 括 i ) 有 多 少 种 方 案 首先说一下DP[i][j][0/1][0/1]表示在树上的i点,一共放了j个监听设备,点i是否被覆盖,点i上是否有监听设备时,监听覆盖完点i的子树(不包括i)有多少种方案 DP[i][j][0/1][0/1]ijiiii

显 然 我 们 在 d f s 中 更 新 时 , 是 从 儿 子 更 新 父 亲 , 所 以 上 面 指 的 覆 盖 是 指 儿 子 对 父 亲 的 覆 盖 。 还 有 我 们 需 要 设 另 一 个 数 组 来 D P , 设 那 个 数 组 为 d p 1 显然我们在dfs中更新时,是从儿子更新父亲,所以上面指的覆盖是指儿子对父亲的覆盖。还有我们需要设另一个数组来DP,设那个数组为dp1 dfsDPdp1

接下来就是恶心的四种情况讨论了。

首先,如果这个点更新后是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;
}

附:好像DP开long long就会爆空间,所以好好写mod吧!!!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值