题意:给你一个n个点m条边的无向无环图,在尽量少的结点放灯,使得所有边都被照亮,每盏灯将照亮以它为一个端点的所有的边,在灯的数量小的前提下,被两盏灯同时照亮的边数应尽量大
思路:大白的题目,本题的优化目标有两个,这是这道题母的亮点:放置的灯数a应尽量少,被两盏灯同时照亮的边数b应尽量大,为了同一起见,我们把后者替换为:恰好被一盏灯同时照亮的边数c应尽量小,然后改用x=Ma+c作为最小化的目标,其中M是一个很大的正整数,当x取到最小值时,x/M的整数部分就是放置的灯数的最小值:x%M就是恰好被一盏灯照亮的边数的最小值。
决策一:节点i不放灯,必须j=1或者i是根节点时才允许作这个决策,如果i不是根的话,还要加上1
决策二:节点i放灯,如果j=0且i不是根节点,那么同样还要加上1
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int MAXN = 1010;
vector<int> adj[1010];
int vis[MAXN][2],d[MAXN][2],n,m;
int dp(int i,int j,int f){
if (vis[i][j])
return d[i][j];
vis[i][j] = 1;
int &ans = d[i][j];
ans = 2000;
for (int k = 0; k < adj[i].size(); k++)
if (adj[i][k] != f)
ans += dp(adj[i][k],1,i);
if (!j && f >= 0)
ans++;
if (j || f < 0){
int sum = 0;
for (int k = 0; k < adj[i].size(); k++)
if (adj[i][k] != f)
sum += dp(adj[i][k],0,i);
if (f >= 0)
sum++;
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++)
adj[i].clear();
for (int i = 0; i < m; i++){
scanf("%d%d",&a,&b);
adj[a].push_back(b);
adj[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);
printf("%d %d %d\n",ans/2000,m-ans%2000,ans%2000);
}
return 0;
}