題目: 點擊打開鏈接
題目大意
給你一個n個點m條邊的無向無環圖,在盡量少的節點上放燈,使得所有邊都被照亮。每盞燈將照亮以它為一個端點的所有邊。
在燈的總數最小的前提下,被兩盞燈同時被照亮的邊數應該盡量大。
思路
這是LRJ《入門經典》上的例題。
這題教會了我一個很有用的技巧:有兩個所求的值要優化,比如讓a盡量小,b也盡量小
那麼可以轉化為讓 M*a+b盡量小,其中M應該是一個比“a的最大值和b的最小值之差”還要大的數
最終的答案為ans/M, ans%M
回到這題,要求放的燈總數最小,被兩盞燈同時照亮的邊數盡量大。
因為每條邊要麼被一盞燈照亮,要麼被兩盞燈照亮,所以可以轉換為:
求:放的燈總數量最少,被一盞燈照亮的邊數盡量少。
就可以變成球 M*a+b 的最小值,a為放置的燈數量,b為被一盞燈照的邊數
f[u][1]表示u點放燈時的整個子樹最小值
f[u][0]表示u點不放燈時的整個子樹最小值
如果u放,那麼u個子結點可以選擇放,也可以不放,選擇其中較小的值。如果選的是不照,就要增加一條只有一個燈照的邊
如果u不放,那麼其子結點就必須選擇要放,而且每條邊都只有一個燈照
下面的我的代碼和書上實現的不一樣,更簡潔
代碼 /**========================================== * This is a solution for ACM/ICPC problem * * @source:uva-10859 Placing Lampposts * @type: 樹形dp * @author: shuangde * @blog: blog.csdn.net/shuangde800 * @email: [email protected] *===========================================*/ #include<iostream> #include<cstdio> #include<algorithm> #include<vector> #include<queue> #include<cmath> #include<cstring> using namespace std; typedef long long int64; const int INF = 0x3f3f3f3f;const int MAXN = 1010; vector<int>adj[MAXN]; bool vis[MAXN]; int n, m;int f[MAXN][2]; const int M = 2000;void dfs(int u) { vis[u] = true; f[u][0] = 0; f[u][1] = M; for(int i = 0; i < adj[u].size(); ++i) { int v = adj[u][i]; if(vis[v]) continue; dfs(v); f[u][0] += f[v][1] + 1; if (f[v][0] < f[v][1]) { f[u][1] += f[v][0] + 1; } else { f[u][1] += f[v][1]; } }}int main(){ int nCase; scanf("%d", &nCase); while(nCase--) { scanf("%d%d", &n, &m); for(int i = 0; i < n; ++i) adj[i].clear(); for(int i = 0; i < m; ++i) { int u, v; scanf("%d%d", &u, &v); adj[u].push_back(v); adj[v].push_back(u); } memset(vis, 0, sizeof(vis)); int ans = 0; for(int i = 0; i < n; ++i) if(!vis[i]){ dfs(i); ans += min(f[i][0], f[i][1]); } printf("%d %d %d\n", ans/M, m-(ans%M), ans%M); } return 0;}