Problem Address:http://poj.org/problem?id=3345
【前言】
前面的两篇都是把树转化为二叉树计算,虽然简单,但是不是很直观。
这份代码是直接用邻接表做。用vector记录邻接的边。
然后对每个结点直接进行背包。
相比起记忆化搜索,直接背包的好处就是少了很多次函数调用,应该是节省了不少时间。
【思路】
这道题比较难处理的是输入。
gets()读取整行数据。
sscanf用来从字符串读入。
stringstream可以用来设置输入流。
然后用map映射结点的索引号。
以0结点作为根节点,连接到每个原来的根。
先对子结点进行背包,再对父结点进行背包。
同时需要更新父结点。如果父结点在包含所有子结点时的费用小于当前费用,则需更新。
【代码】
#include <iostream>
#include <map>
#include <sstream>
#include <vector>
#include <string>
using namespace std;
const int maxn = 200;
const int MAX = 99999999;
map<string, int> ma;
vector<int> adj[maxn+5];
bool visited[maxn+5];
int diamond[maxn+5];
int dp[maxn+5][maxn+5];
int n, m;
int solve(int v)
{
visited[v] = true;
int i, j, k;
int t, u;
int num = 1;
for (i=0; i<=n; i++)
dp[v][i] = MAX;
dp[v][0] = 0;
for (i=0; i<adj[v].size(); i++)
{
u = adj[v][i];
if (visited[u]) continue;
num += solve(u);
for (j=n; j>0; j--)
{
for (k=1; j-k>=0; k++)
{
if (dp[v][j-k]+dp[u][k]<dp[v][j])
dp[v][j] = dp[v][j-k] + dp[u][k];
}
}
}
if (dp[v][num]>diamond[v])
dp[v][num] = diamond[v];
return num;
}
int main()
{
char str[1000];
char name[105];
int index;
int i, j, tn, tc;
while(gets(str))
{
if (str[0]=='#') break;
sscanf(str, "%d %d", &n, &m);
index = 1;
ma.clear();
for (i=0; i<=n; i++)
{
adj[i].clear();
visited[i] = false;
}
for (i=0; i<n; i++)
{
scanf("%s", name);
if (ma.find(name)==ma.end())
{
ma[name] = index;
index++;
}
tn = ma[name];
scanf("%d", &diamond[tn]);
gets(str);
stringstream ss(str);
while(ss>>name)
{
if (ma.find(name)==ma.end())
{
ma[name] = index;
index++;
}
tc = ma[name];
adj[tn].push_back(tc);
adj[tc].push_back(tn);
if (!visited[tc])
visited[tc] = true;
}
}
diamond[0] = MAX;
for (i=1; i<=n; i++)
{
if (!visited[i])
adj[0].push_back(i);
}
for (i=0; i<=n; i++)
visited[i] = false;
solve(0);
int ans = MAX;
for (i=m; i<=n; i++)
if (dp[0][i]<ans)
ans = dp[0][i];
printf("%d\n", ans);
}
return 0;
}