洛谷2279:消防局的设立
题意:
- 给定一棵树,树上的某些节点设立消防局,这个消防局可以覆盖距离不超过2的节点。
- 问有最少有多少个消防局可以覆盖整棵树。
数据范围:
思路:
- 树形\(dp\)。
- 设\(f(i,state)\)表示节点\(i\)在\(state\)状态下表示最小的消防站个数。
- \(f(i,0)\)表示选择\(i\)为消防局。
- \(f(i,1)\)表示至少选了一个\(i\)的一个儿子为消防局。
- \(f(i,2)\)表示至少选了一个\(i\)的孙子为消防局。
- \(f(i,3)\)表示\(i\)的所有儿子节点一定被消防局覆盖。
- \(f(i,4)\)表示\(i\)的所有孙子节点一定被消防局覆盖。
- 转移方程:
- \(f(i,0)=1+\sum min(f(j,0...4))\)。
- 解释:由于\(i\)为消防站儿子和孙子就无所谓了,因此从0~4中寻找一个\(min\)来转移,最后\(+1\)(自己的开销)。这里\(j\)对应的是儿子。
- \(f(i,1)=min(f(k,0)+\sum_{j!=k}min(f(j,0...3))\)。
- 解释:\(f(i,1)\)表示至少选了一个\(i\)的儿子为消防局,那么不能保证所有的孙子都能被覆盖,只能保证父亲和所有兄弟被覆盖到,同时需要\(i\)的孙子一定要被全部覆盖。\(i\)的孙子被全部覆盖,状态0~3满足。这里\(k\)对应的是其中一个儿子,\(j\)对应的是除了\(k\)以外的其他儿子。
- \(f(i,2)=min(f(k,1)+\sum_{j!=k}min(f(j,0...2))\)。
- 解释:选了一个孙子,那么所有的儿子应该被全部覆盖掉,儿子被覆盖的状态对应0~2。这里\(k\)对应的是其中一个儿子,\(j\)对应的是除了\(k\)以外的其他儿子。
- \(f(i,3)=\sum min(f(j,0...2))\)。
- 解释:要让所有的儿子一定被覆盖,那么儿子的0~2状态都符合条件。
- \(f(i,4)=\sum min(f(j,0...3))\)
- 解释:孙子全部被覆盖,那么儿子的0~3状态都符合条件,所以取个\(min\)。
- 发现了所有的状态都是0~?这样的形式,所以令\(f(i,j)\)表示\(min(f(i,0...k))\)
- 因此有:
- \(f(i,0)=1+\sum f(j,4)\).
- \(f(i,1)=f(i,4)+min(f(j,0)-f(j,3))\).
- 选一个儿子作为消防局,需要保证所有孙子应该被覆盖,所以加上\(f(i,4)\)。对于\(f(i,4)\),选那个儿子处于状态0~3,但是要把他转变为只选择自己,所以取\(f(j,0)-f(j,3)\)。
- \(f(i,2)=f(i,3)+min(f(j,1)-f(j,2))\).
- 选一个孙子作为消防局,需要保证所有儿子被覆盖,所以加上 \(f(i,3)\)。对于\(f(i,3)\),儿子处于\(f(j,2)\)状态,但是需要将一个儿子变为0~1状态保证父亲节点被覆盖,所以取\(f(j,1)-f(j,2)\)。
- \(f(i,3)=\sum f(j,2)\).
- \(f(i,4)=\sum f(j,3)\).
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1000 + 10;
const int INF = 0x3f3f3f3f;
int a[maxn], n;
bool G[maxn][maxn];
int f[maxn][10];
int main()
{
scanf("%d", &n);
for(int i = 2, x; i <= n; i++)
{
scanf("%d", &x);
G[x][i] = 1;
}
for(int i = n, x1, x2; i >= 1; i--)
{
x1 = x2 = INF;
f[i][0] = 1;
for(int j = 1; j <= n; j++)
{
if(G[i][j])
{
f[i][0] += f[j][4];
f[i][3] += f[j][2];
f[i][4] += f[j][3];
x1 = min(x1, f[j][0]-f[j][3]);
x2 = min(x1, f[j][1]-f[j][2]);
}
}
f[i][1] = f[i][4] + x1;
f[i][2] = min(f[i][3]+x2, min(f[i][0], f[i][1]));
f[i][3] = min(f[i][2], f[i][3]);
f[i][4] = min(f[i][3], f[i][4]);
}
cout << f[1][2] << endl;
return 0;
}