POJ 3345 Bribing FIPA(树形DP)
Description
There is going to be a voting at FIPA (Fédération Internationale de Programmation Association) to determine the host of the next IPWC (International Programming World Cup). Benjamin Bennett, the delegation of Diamondland to FIPA, is trying to seek other delegation's support for a vote in favor of hosting IWPC in Diamondland. Ben is trying to buy the votes by diamond gifts. He has figured out the voting price of each and every country. However, he knows that there is no need to diamond-bribe every country, since there are small poor countries that take vote orders from their respected superpowers. So, if you bribe a country, you have gained the vote of any other country under its domination (both directly and via other countries domination). For example, if C is under domination of B, and B is under domination of A, one may get the vote of all three countries just by bribing A. Note that no country is under domination of more than one country, and the domination relationship makes no cycle. You are to help him, against a big diamond, by writing a program to find out the minimum number of diamonds needed such that at least m countries vote in favor of Diamondland. Since Diamondland is a candidate, it stands out of the voting process.
Input
The input consists of multiple test cases. Each test case starts with a line containing two integers n (1 ≤ n ≤ 200) and m (0 ≤ m ≤ n) which are the number of countries participating in the voting process, and the number of votes Diamondland needs. The next n lines, each describing one country, are of the following form:
CountryName DiamondCount DCName1 DCName1 ...
CountryName, the name of the country, is a string of at least one and at most 100 letters and DiamondCount is a positive integer which is the number of diamonds needed to get the vote of that country and all of the countries that their names come in the list DCName1 DCName1 ... which means they are under direct domination of that country. Note that it is possible that some countries do not have any other country under domination. The end of the input is marked by a single line containing a single # character.
Output
For each test case, write a single line containing a number showing the minimum number of diamonds needed to gain the vote of at least m countries.
Sample Input
3 2 Aland 10 Boland 20 Aland Coland 15 #
Sample Output
20
题目:
给定由若干个树组成的森林, 树上的边是有向边, 树上的每个节点都有一个代价. 若要得到某个节点, 需要付出该节点对应的代价, 若该节点拥有后继, 那么后继的节点也都能获得. 求解使用最少的代价取得至少 m 个节点
思路:
1. 树形DP, 在森林里的每个树上分配配额.
2. dp[i][j] 表示在第 i 个节点上获得 j 个节点所需要的最少代价. 那么 dp[i][j] = min(dp[i][j], dp[v][k]+dp[i][j-k])
3. 根据上面的状态转移方程式, 依然是个典型的树形 DP, 依然是 3 重循环, dfs(u) 即可, 甚至不用 pre, 因为给定的树边都是有向边
总结:
1. sscanf 用法
while( gets(str) && str[0] != '#') { sscanf(str, "%d%d", &n,&m);
2. stringstream 用法, 需要先引入 sstream
gets(str); stringstream ss(str); while(ss >> name)
细节:
1. 输入比较坑爹. '#'加的完全是画蛇添足, 在处理这样的输入数据时, 用 sscanf是极为适合的, 因其有类似回滚的能力
2. gets(str) 函数可以从一行中的某一个位置处重新开始读取, 而不是必定读取一整行
3. 题目给出一个 m, 但是我们仍需要求解 dp[0][n]. 因为可能会出现这种情况, 直接贿赂父节点比贿赂其后继节点划算而使得 dp[0][i] > dp[0][j], j>i). dp[0][i]记录的是恰好得到 i 个节点的代价. 这个地方WA过
4. 输入给出的是几棵树, 但代码将其组成了一个森林, 这样的直接一次 dfs(0) 即可得到结果. 第一次看别人代码时没看懂. 这个技巧是有普适性的, 以后做树形 DP 时也要考虑能否使用
5. INF 的设置, 这道题没有像 communication system 那样, 将 dp 初始化为 -1, 而是简单的将其初始化为 INF, 以便于直接使用 min, 减少了很多判断(当然就会填满二维矩阵). 但是要注意, INF 不能太大, 要确保 INF+INF 不会溢出
6. POJ 似乎不支持 unordered_map, 第一次提交时 compile error 了
7. cost[0] 需要初始化为 INF
8. 下面代码中变量 j 的遍历顺序是从 n->1. 这就防止了同一个子树下的节点被取两次的问题. 同时 i 的取值是 u 的各个孩子节点, 抽象来看, 就是完全背包问题
代码
#include <iostream> #include <sstream> #include <vector> #include <string> #include <map> using namespace std; const int MAXN = 250; const int INF = 1e9; map<string, int> s2i; vector<int> graph[MAXN]; int cost[MAXN]; bool root[MAXN]; int dp[MAXN][MAXN]; int n, m; string name; int diamond; int dfs(int u) { // 有向图, 不需要考虑 visited or pre for(int i = 1; i <= n; i ++) dp[u][i] = INF; dp[u][0] = 0; int child = 1; for(int i = 0; i < graph[u].size(); i++) { int v = graph[u][i]; child += dfs(v); for(int j = n; j >= 0; j --) { for(int k = 0; k <= j; k ++) { dp[u][j] = min(dp[u][j], dp[u][j-k]+dp[v][k]); // INF+INF? } } } dp[u][child] = min(dp[u][child], cost[u]); return child; } int main() { freopen("E:\\Copy\\ACM\\poj\\3345_v2\\in.txt","r", stdin); char str[1000]; while( gets(str) && str[0] != '#') { sscanf(str, "%d%d", &n,&m); int id = 1; s2i.clear(); memset(root, 1, sizeof(root)); for(int i = 0; i <= n; i ++) graph[i].clear(); for(int i = 0; i < n; i ++) { //cin >>name>>diamond; scanf("%s %d", &str, &diamond); if(s2i.find(str) == s2i.end()) s2i[str] = id++; int u = s2i.find(str)->second; cost[u] = diamond; gets(str); stringstream ss(str); while(ss >> name) { if(s2i.find(name) == s2i.end()) s2i[name] = id++; int v = s2i.find(name)->second; root[v] = false; graph[u].push_back(v); } } for(int i = 1; i <= n; i ++) if(root[i]) graph[0].push_back(i); cost[0] = INF; dfs(0); int ans = INF; for(int i = m; i <= n; i ++) ans = min(ans, dp[0][i]); cout << ans << endl; // main function } return 0; }