题目大意:给一颗无向图树,每个节点都可以放灯,灯能点亮相邻的边,求怎么样才能把所有边都点亮。
如果有多种方案:给出一个能够让有两盏灯同时找到路情况最多的方案。
解析:
如果去掉后面的条件,就成为了一个很简单的树上dp。如何处理同时两盏灯找到的情况呢?
通常情况下:我们会将无根树变为有根树,建立一个dp[i][j] ,表示遍历为所有i及其子节点后,情况为j(0/1,放/不放)时的方案总数。 我们对转移函数进行修改:dp[i][j] = Cx + y,其中C为一个很大的数,使得无论取何值,y都发撼动x占主导地位的情况。
在这里,我们设置C = 2000, x表示放置灯的个数,y表示仅有一盏灯时照到的个数。
答案相应的变成了 ans/C, m-ans%C, ans%C,一个方程,全部解决。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1010;
const int M = 2000;
vector<int> adj[maxn];
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 += M;
for (int k = 0 ; k < adj[i].size(); ++k)
{
int t = adj[i][k];
if(t != f)
ans += dp(t,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;
cin >> T;
while(T--)
{
cin >> n >> m;
for (int i = 0 ; i < n ; ++i) adj[i].clear();
memset(vis,0,sizeof(vis));
memset(d,0,sizeof(d));
for (int i = 0 ; i < m ; ++i)
{
cin >> a >> b;
adj[a].push_back(b);
adj[b].push_back(a);
}
int ans = 0;
for (int i = 0 ; i < n ; ++i)
if(!vis[i][0])
ans += dp(i,0,-1);
cout << ans/M << ' ' << m - ans%M << ' '<< ans%M << endl;
}
return 0;
}