首先,本题的优化目标有两个:放置的街灯a应该尽量少;被两灯同时照亮的边数b应该尽量大。为了统一起见,我们把后者替换为:恰好被一盏灯照亮的边数c应该尽量小,然后改用x = Ma+c作为优化目标,其中一个M为很大的正整数。当x取到最小值时,x/M的整数部分是放置街灯的最小值,x%M为恰好被一盏灯照亮的边数的最小值。
一般来说,如果有两个需要优化的目标v1和v2时,要求首先满足v1最小,在v1最小的情况的下v2最小,则可以将二者组合成一个量Mv1+v2,其中M是一个比“v2的最大理论值和v2的最小理论值之差”还要大的数,这样,只要两个解的v1不同,则v2不管相差多少,都是v11起到决定性的作用;只有当v1相同时,才取决于v2。本题中M取2000.
/********************
* Author :fisty
* data:2014-12-10
* uva 10859
* 树形DP
* ********************/
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
#define MAX_N 1010
vector<int> G[MAX_N];
int vis[MAX_N][2], d[MAX_N][2], n, m;
int dp(int i, int j, int fa){
//j是i的父节点状态(1 为有灯, 0 为无灯)
//fa是i的父节点
if(vis[i][j]) return d[i][j];
vis[i][j] = 1;
int& ans = d[i][j]; //ans是对dp进行引用
ans = 2000; //灯的数量加1,x加2000
//决策1: i结点放灯,d(i,j) = sum{d(k,1)|k取遍i的所有子节点} + 2000;
for(int k = 0;k < G[i].size(); k++){
if(G[i][k] != fa){
ans += dp(G[i][k], 1, i); //
}
}
if(fa >= 0 && !j) ans++; //如果i不是根节点还要加上i与父节点的边
//决策2:i结点不放灯(只有在i是根节点或者父结点放了灯才可以执行)
//d(i,j) = sum{d(k, i)|k取遍所有i的所有子结点}+2000
if(j != 0 || fa == -1){
//i是根或者i 父节点放了灯
int sum = 0;
for(int k = 0;k < G[i].size(); k++){
if(G[i][k] != fa){
sum += dp(G[i][k], 0, i);
}
}
if(fa >= 0) sum++; //i不是根
ans = min(ans , sum); //在两个决策中选一个最小值
}
return ans;
}
int main(){
int t, a, b;
scanf("%d", &t);
while(t--){
scanf("%d%d", &n, &m);
for(int i = 0;i < n; i++) G[i].clear();
for(int i = 0;i < m; i++){
scanf("%d%d", &a, &b);
G[a].push_back(b);
G[b].push_back(a);
}
memset(vis, 0, sizeof(vis));
int ans = 0;
for(int i = 0;i < n; i++){
if(!vis[i][0]){
ans += dp(i, 0, -1); //i是根没有父节点,为-1;
}
}
printf("%d %d %d\n", ans / 2000, m - ans % 2000, ans % 2000);
}
return 0;
}