题目:http://poj.org/problem?id=1947
题意:给一颗树包含N个节点,问你最少剪去几个边可以得到一颗包含P个节点的子树。
解题思路:这个题目让我想了一天加一晚上,开始一直想用DP[i][j]表示以i为父节点的树,保留j个子节点至少要剪的边数,结果一直想不出思路,实在没办法,在网上找的解题报告很多也是这个想法,但是在我的不解努力中,终于找到一个独树一帜的解题思路:用DP[i][j]表示,以i为父节点的树,删去j个点,至少要剪去几条边。这样就和原来的思路 相反了,然后解题就一下子简单了好多,直接就是一个很简单很简单的树型dp了,也就是一个很裸的依赖背包了。然后自己写了一个,感觉比参考的代码还要高效!!
解题感想:遇到问题时,如果用一种常规的思路一直解决不了,那么也许用一个相反的思路就可以把问题变的很简单,很简单了。
代码如下:
//1947 Accepted 392K 0MS C++ 1271B 2012-07-24 08:04:30
#include <stdio.h>
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;
#define N 200
#define M (1<<30)
vector <int> tree[N];
int num,remain,root,ans;
int dp[N][N];//dp[i][j]表示以i为根,删除j个还在最少需要删几个边
int dfs(int key)
{
int length=tree[key].size(),size=1;
int i,j,k,v,p;
for(i=0;i<length;i++)
{
v=tree[key][i];
p=dfs(v); size+=p;
for(j=num-remain;j>0;j--)
for(k=1;k<=min(j,p);k++)
dp[key][j]=min(dp[key][j],dp[key][j-k]+dp[v][k]);
}
dp[key][size]=1;
if(key!=root&&size>=remain)
ans=min(ans,dp[key][size-remain]+1);
tree[key].clear();
return size;
}
void init()
{
int i,x,y; bool de[N];
ans=M;
memset(de,true,sizeof(de));
for(i=1;i<num;i++)
{
scanf("%d%d",&x,&y);
tree[x].push_back(y);
de[y]=false;
}
for(i=1;i<num;i++)
if(de[i])
{
root=i;break;
}
for(i=1;i<=num;i++)
{
dp[i][0]=0;
for(int j=1;j<=num;j++)
dp[i][j]=M;
}
}
int main()
{
while(cin>>num>>remain)
{
init();
ans=min(ans,dp[root][dfs(root)-remain]);
cout<<ans<<endl;
}
}
nyoj代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
const int N = 510;
const int MAX = (1 << 30);
using namespace std;
struct Edg{
int nod, next;
}edg[2 * N];
int nod[N], k;
bool vis[N];
int dp[N][N];
int ans, n, remain;
void add_edg(int x, int y){
edg[k].nod = y;
edg[k].next = nod[x];
nod[x] = k ++;
}
void init(){
int x, y;
k = 0; ans = MAX;
memset(vis, true, sizeof(vis));
memset(nod, -1, sizeof(nod));
cin >> n >> remain;
for(int i = 0; i < n - 1; i ++){
cin >> x >> y;
add_edg(x, y);
add_edg(y, x);
}
for(int i = 1;i <= n ;i ++){
dp[i][0] = 0;
for(int j = 1; j <= n; j ++)
dp[i][j] = MAX;
}
}
int dfs(int key){
vis[key] = false;
int v, p, size = 1;
for(int i = nod[key]; i != -1; i = edg[i].next){
v = edg[i].nod;
if(!vis[v]) continue;
p = dfs(v); size += p;
for(int j = n - remain; j > 0; j --){
for(int k = 1; k <= min(p, j);k ++){
dp[key][j] = min(dp[key][j], dp[key][j - k] + dp[v][k]);
}
}
}
if(key == 1) dp[key][size] = 0;
else dp[key][size] = 1;
if(key != 1 && size >= remain)
ans = min(ans, dp[key][size - remain] + 1);
return size;
}
int main(){
//freopen("1.txt", "r", stdin);
//freopen("2.txt", "w", stdout);
int T, cas = 1;
cin >> T;
while(T --){
init();
ans = min(ans,dp[1][dfs(1) - remain]);
printf("case #%d: %d\n",cas ++, ans);
}
}