树状dp,不简单。
题意:给出一棵有根树,问最少切断几条边可以得到有i个结点的子树(连通分支)。
采用左儿子右兄弟的存储方式,
dp用两个数组:
f[i][j]表示以i为根的子树,形成以本子树根结点为根(不包含本子树以外结点),共j个结点的子树所需的最小消耗。这里并没有计算切断i与其父亲连接的边的消耗。
g[i][j]表示以i节点的父亲为根的,i结点以及i的父亲的子结点链中的i以后的结点做根的子树,构成共j个结点的以i的父亲为根(不包含本子树以外结点)的子树所需的最小消耗(i的兄弟们在形成子树时与i的父亲相连,即计算消耗时无需切断与i父亲的连接,但j个结点中不包含i的父亲)
有状态转移方程:g[root][count] = min(g[tree[root].next][count - i] + f[root][i], g[root][count]);
f[root][count +1] = g[tree[root].son][count];
我们只需要把f[i][0]初始化为1,就可以直接把切断整个以root为根的子树的情况融合在状态转移方程中了。意思就是在以root为根的子树中取0个点,需要切断与root的连接,消耗1。
用递归的方法来填写两个数组。可以形象地认为最外层循环是枚举count,对于每个count,对树进行从下到上,对子树链进行从右到左的填写。
其实用记忆化搜索的方式会更简便易懂。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#include
<
iostream
>
#include < cstdio >
#include < cstdlib >
#include < cstring >
using namespace std;
#define maxn 155
#define inf 0x3f3f3f3f
struct Node
{
int son, next, father, num;
} tree[maxn];
int n, p, root;
int f[maxn][maxn], g[maxn][maxn];
void work( int root, int count)
{
if (root == 0 )
return ;
work(tree[root].son, count);
work(tree[root].next, count);
g[root][count] = inf;
if (tree[root].next)
for ( int i = 0 ; i <= count; i ++ )
g[root][count] = min(g[tree[root].next][count - i] + f[root][i],
g[root][count]);
else
g[root][count] = f[root][count];
f[root][count + 1 ] = inf;
if (count + 1 > tree[root].num)
return ;
f[root][count + 1 ] = g[tree[root].son][count];
}
void cal( int root)
{
if (root == 0 )
return ;
cal(tree[root].son);
cal(tree[root].next);
int temp = tree[root].son;
tree[root].num = 1 ;
while (temp)
{
tree[root].num += tree[temp].num;
temp = tree[temp].next;
}
}
int main()
{
// freopen("t.txt", "r", stdin);
scanf( " %d%d " , & n, & p);
memset(tree, 0 , sizeof (tree));
for ( int i = 0 ; i < n - 1 ; i ++ )
{
int a, b;
scanf( " %d%d " , & a, & b);
tree[b].next = tree[a].son;
tree[a].son = b;
tree[b].father = a;
}
for ( int i = 1 ; i <= n; i ++ )
if (tree[i].father == 0 )
{
root = i;
break ;
}
cal(root);
for ( int i = 1 ; i <= n; i ++ )
f[i][ 0 ] = 1 ;
for ( int i = 0 ; i < p; i ++ )
work(root, i);
int ans = inf;
for ( int i = 1 ; i <= n; i ++ )
if (i == root)
ans = min(ans, f[i][p]);
else
ans = min(ans, f[i][p] + 1 );
printf( " %d\n " , ans);
return 0 ;
}
#include < cstdio >
#include < cstdlib >
#include < cstring >
using namespace std;
#define maxn 155
#define inf 0x3f3f3f3f
struct Node
{
int son, next, father, num;
} tree[maxn];
int n, p, root;
int f[maxn][maxn], g[maxn][maxn];
void work( int root, int count)
{
if (root == 0 )
return ;
work(tree[root].son, count);
work(tree[root].next, count);
g[root][count] = inf;
if (tree[root].next)
for ( int i = 0 ; i <= count; i ++ )
g[root][count] = min(g[tree[root].next][count - i] + f[root][i],
g[root][count]);
else
g[root][count] = f[root][count];
f[root][count + 1 ] = inf;
if (count + 1 > tree[root].num)
return ;
f[root][count + 1 ] = g[tree[root].son][count];
}
void cal( int root)
{
if (root == 0 )
return ;
cal(tree[root].son);
cal(tree[root].next);
int temp = tree[root].son;
tree[root].num = 1 ;
while (temp)
{
tree[root].num += tree[temp].num;
temp = tree[temp].next;
}
}
int main()
{
// freopen("t.txt", "r", stdin);
scanf( " %d%d " , & n, & p);
memset(tree, 0 , sizeof (tree));
for ( int i = 0 ; i < n - 1 ; i ++ )
{
int a, b;
scanf( " %d%d " , & a, & b);
tree[b].next = tree[a].son;
tree[a].son = b;
tree[b].father = a;
}
for ( int i = 1 ; i <= n; i ++ )
if (tree[i].father == 0 )
{
root = i;
break ;
}
cal(root);
for ( int i = 1 ; i <= n; i ++ )
f[i][ 0 ] = 1 ;
for ( int i = 0 ; i < p; i ++ )
work(root, i);
int ans = inf;
for ( int i = 1 ; i <= n; i ++ )
if (i == root)
ans = min(ans, f[i][p]);
else
ans = min(ans, f[i][p] + 1 );
printf( " %d\n " , ans);
return 0 ;
}