【洛谷4516】[JSOI2018] 潜入行动(树上背包)
大致题意: 给定一棵树,让你选择恰好\(k\)个点,使得与每个点相邻的节点(不包括该节点自身)中都有至少一个被选中的点,求方案数。
树上背包
感觉就是一道裸题啊。。。
树上背包的题目嘛,看着像是\(O(nk^2)\),实际上就是\(O(nk)\)。(这个复杂度我至今不会证)
直接设\(f_{i,j,0/1,0/1}\)表示在\(i\)的子树中选了\(j\)个点、是否选择\(i\)、\(i\)是否已满足条件时,\(i\)子树中所有点(不包括\(i\)自身)满足条件的方案数。
转移的式子又臭又长,放上来不太美观,不过个人感觉还是很容易推的。
因此直接上代码了。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define K 100
#define X 1000000007
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n,m,ee,lnk[N+5];struct edge {int to,nxt;}e[N<<1];
class TreeDP
{
private:
int f[N+5][K+5][2][2],g[N+5],t[K+5][2][2];
I void DP(CI x,CI lst=0)//树上背包
{
f[x][0][0][0]=f[x][1][1][0]=g[x]=1;//初始化
for(RI i=lnk[x],y,j,k;i;i=e[i].nxt) if((y=e[i].to)^lst)//枚举子节点
{
for(DP(y,x),j=0;j<=min(g[x],m);++j) t[j][0][0]=f[x][j][0][0],
t[j][0][1]=f[x][j][0][1],t[j][1][0]=f[x][j][1][0],t[j][1][1]=f[x][j][1][1],//复制一份
f[x][j][0][0]=f[x][j][0][1]=f[x][j][1][0]=f[x][j][1][1]=0;//清空
for(j=0;j<=min(g[x],m);++j) for(k=0;k<=min(g[y],m-j);++k)//枚举,注意上界
Inc(f[x][j+k][0][0],1LL*t[j][0][0]*f[y][k][0][1]%X),//0 0
Inc(f[x][j+k][0][1],(1LL*t[j][0][0]*f[y][k][1][1]+
1LL*t[j][0][1]*(f[y][k][0][1]+f[y][k][1][1]))%X),//0 1
Inc(f[x][j+k][1][0],1LL*t[j][1][0]*(f[y][k][0][0]+f[y][k][0][1])%X),//1 0
Inc(f[x][j+k][1][1],(1LL*t[j][1][0]*(f[y][k][1][0]+f[y][k][1][1])+
1LL*t[j][1][1]*(0LL+f[y][k][0][0]+f[y][k][0][1]+f[y][k][1][0]+f[y][k][1][1]))%X);//1 1
g[x]+=g[e[i].to];//更新子树大小
}
}
public:
I void Solve() {DP(1),printf("%d\n",(f[1][m][0][1]+f[1][m][1][1])%X);}//注意最终答案
}T;
int main()
{
RI i,x,y;for(scanf("%d%d",&n,&m),i=1;i^n;++i) scanf("%d%d",&x,&y),add(x,y),add(y,x);//读边建树
return T.Solve(),0;
}
待到再迷茫时回头望,所有脚印会发出光芒