Time Limit: 1000MS | Memory Limit: 30000K | |
Total Submissions: 10607 | Accepted: 4863 |
Description
Input
Output
Sample Input
11 6 1 2 1 3 1 4 1 5 2 6 2 7 2 8 4 9 4 10 4 11
Sample Output
2
Hint
Source
卡了一阵子……只能说对树型dp的理解还不是非常深刻……
题目大意:一棵树,问最少砍几条边,能得到一个p个节点的子树。
这样可以想到用dp[i][j]表示节点i为根,得到j个节点的子树所需要砍掉的最少边数。
这样对于父节点u,遍历孩子节点v1.2.3...时,每当新加一个子节点v,可以选取v中0.1.2.3....k(k <= v子树的节点数) 这样对于dp[u][i] 可以通过dp[u][j]外加从v中选取的节点(i-j个)来组成,也就是转移方程。
如果上面的想明白了,会发现有新的问题,dp[i][j]表示以i为根的j个节点的子树所砍掉的边数,但不能包含i和其父亲(如果i不是根)的边,否则在遍历i的父亲那层中会导致转移出错,因为在这层,它与i之间的边不可砍掉(除非整个i子树都不选取)。所以我的方法就是做了一堆啰嗦的判断……
大体这样取个最小值就行了,要注意p个节点的子树可以以原树中任何一个节点为根,也就是答案要在遍历的过程中找。
代码如下:
#include <iostream> #include <cmath> #include <vector> #include <cstdlib> #include <cstdio> #include <cstring> #include <queue> #include <stack> #include <list> #include <algorithm> #include <map> #include <set> #define LL long long #define Pr pair<int,int> #define fread() freopen("in.in","r",stdin) #define fwrite() freopen("out.out","w",stdout) using namespace std; const int INF = 0x3f3f3f3f; const int msz = 10000; const int mod = 1e9+7; const double eps = 1e-8; bool can[155][155]; int dp[155][155]; int n,p,mn; void dfs(int u) { memset(dp[u],INF,sizeof(int)*n); dp[u][0] = dp[u][1] = 0; bool f = 0; for(int i = 1; i <= n; ++i) { if(!can[u][i]) continue; f = 1; dfs(i); for(int k = p; k >= 1; --k) { if(dp[u][k] != INF) dp[u][k]++; for(int j = 1; j <= p && j < k && dp[i][j] != INF; ++j) { if(dp[u][k-j] != INF) dp[u][k] = min(dp[u][k-j]+dp[i][j],dp[u][k]); } } } //如果u不是整个树的根,那么在dp数组中不计入其与父节点的边(为了方便转移),在取答案时需要加上(断绝它与父节点的联系) if(u != 1) mn = min(mn,dp[u][p]+1); else mn = min(mn,dp[u][p]); } int main() { //fread(); //fwrite(); int u,v; while(~scanf("%d%d",&n,&p)) { memset(can,0,sizeof(can)); for(int i = 1; i < n; ++i) { scanf("%d%d",&u,&v); can[u][v] = 1; } mn = INF; dfs(1); printf("%d\n",mn); } return 0; }