• [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 (1le u,vle n)(1≤u,v≤n),表示树中的一条边。

    输出格式
    输出一行,表示满足条件的方案数。因为答案可能很大,你只需要输出答案 ( ext{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
    ",(dp[1][m][1][0]+dp[1][m][1][1])%mod);
    	return 0;
    }
    

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

  • 相关阅读:
    Python OpenCV 常用操作
    Conda Cheatsheet | 速查表
    Loadrunner解决启动浏览器后页面显示空白
    26个ASP.NET常用性能优化方法
    C# Foreach用法
    体验ASP.NET MVC3 表单令牌功能!
    基于.Net(C#开发)平台的三层框架架构软件的设计与实现
    去掉浏览器中a标签的虚线
    Microsoft Dynamics CRM 4.0 序列号
    编写 Cookie
  • 原文地址:https://www.cnblogs.com/2017gdgzoi44/p/11780497.html
Copyright © 2020-2023  润新知