题目大意:给出一个n个点m条边的无向环路,在尽量少的节点上放置灯笼,使得所有的边都被照亮,每盏灯将照亮以它为一个端点的所有边,在灯的总数最小的前提下,被两盏灯同时照亮的边数应该最大
解题思路:n个点m条边的无向环路可以看成是一个森林。枚举每个节点,这里有两种情况:
1.该节点点亮:该节点点亮的话,就表示该节点和其子节点的灯可以不必点亮
2.该节点不点亮:只有当该节点的父节点点亮或者该节点是根节点时才允许
这里假设照亮的灯的数量为a,被一盏灯照亮的边数为c,一个很大的数为M(M是一个比c的最大理论值和c的最小理论值之差还要大的数),这样的话,设Y = aM + c,因为M的性质,所以a = Y / M, c = Y % M,这样的话,只要保证Y取最小值就可以得到最小的点亮的灯数和最大的被两盏灯照亮的边数
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
#define maxn 1010
#define M 2000
vector<int> tree[maxn];
int dp[maxn][2], vis[maxn][2];
int dfs(int i, int j, int f) {
if(vis[i][j])
return dp[i][j];
vis[i][j] = 1;
int &ans = dp[i][j];
int size = tree[i].size();
ans = M;
for(int k = 0; k < size; k++)
if(tree[i][k] != f)
ans += dfs(tree[i][k],1,i);
if(f >= 0 && !j)
ans++;
if(j || f < 0) {
int tmp = 0;
for(int k = 0; k < size; k++)
if(tree[i][k] != f)
tmp += dfs(tree[i][k],0,i);
if(f >= 0)
tmp++;
ans = min(ans,tmp);
}
return ans;
}
int main() {
int test, n, m, a, b;
scanf("%d",&test);
while(test--) {
scanf("%d%d", &n, &m);
memset(vis,0,sizeof(vis));
for(int i = 0; i < n; i++)
tree[i].clear();
for(int i = 0; i < m; i++) {
scanf("%d%d",&a, &b);
tree[a].push_back(b);
tree[b].push_back(a);
}
int ans = 0;
for(int i = 0; i < n; i++)
if(!vis[i][0])
ans += dfs(i,0,-1);
printf("%d %d %d\n",ans / M, m - ans % M, ans % M);
}
return 0;
}