题目描述
2020年,人类在火星上建立了一个庞大的基地群,总共有n个基地。起初为了节约材料,人类只修建了n-1条道路来连接这些基地,并且每两个基地都能够通过道路到达,所以所有的基地形成了一个巨大的树状结构。如果基地A到基地B至少要经过d条道路的话,我们称基地A到基地B的距离为d。
由于火星上非常干燥,经常引发火灾,人类决定在火星上修建若干个消防局。消防局只能修建在基地里,每个消防局有能力扑灭与它距离不超过2的基地的火灾。
你的任务是计算至少要修建多少个消防局才能够确保火星上所有的基地在发生火灾时,消防队有能力及时扑灭火灾。
输入输出格式
输入格式:
输入文件名为input.txt。
输入文件的第一行为n (n<=1000),表示火星上基地的数目。接下来的n-1行每行有一个正整数,其中文件第i行的正整数为a[i],表示从编号为i的基地到编号为a[i]的基地之间有一条道路,为了更加简洁的描述树状结构的基地群,有a[i]<i。
输出格式:
输出文件名为output.txt
输出文件仅有一个正整数,表示至少要设立多少个消防局才有能力及时扑灭任何基地发生的火灾。
输入输出样例
6 1 2 3 4 5
2
记住一句话: 能实现这个目标的所有途径都给老子找出来,拿出来互相比较大小!!! 状态: dp[i][0]:选自己 dp[i][1]:选了至少一个儿子 dp[i][2]:选了至少一个孙子 -----------------------------------这三种是覆盖了自己的 dp[i][3]: 儿子孙子全部覆盖 dp[i][4]:孙子全部覆盖 -----------------------------------这两种并没有覆盖自己 建议画一棵深度为3的完全二叉树直观观察状态 否则转移方程比较难懂 初始转移方程: dp[i][0] = 1+Σmin(dp[j][0...4]); 要使选了根节点之后合法(整棵子树包括根节点被覆盖)必须使儿子的孙子全部覆盖 0~4状态满足 dp[i][1] = min( dp[k][0] + Σ(j != k)min(dp[j][0...3]) ); 要使选了一个儿子之后合法 由于儿子只可以覆盖到兄弟 所以孙子一定要全部被覆盖 即儿子的儿子一定覆盖 0~3满足 dp[i][2] = min( dp[k][1] + Σ(j != k)min(dp[j][0...2]) ); 使选了一个孙子之后合法 由于孙子最多只能覆盖到当前节点 所以儿子一定全部覆盖 即所有儿子本身要被覆盖 0~2满足 dp[i][3] = Σdp[j][0...2]; 要使儿子及孙子全部被覆盖 即儿子本身要被覆盖 0~2满足 dp[i][4] = Σdp[j][0...3]; 要使孙子全部被覆盖 即儿子的儿子要全部被覆盖 0~3满足 ::注意每种状态由儿子转移过来所以根的情况 要转化成对于儿子来说的情况 然后改进状态 因为每种转移方程至少有三种可能最后取其中较小的 故时间效率较低 令dp[i][k]表示min(dp[i][0],dp[i][1]....dp[i][k])且k>=2 因为上述转移方程最少都是0~2状态 那么转移方程就大幅度化简了: dp[i][0] = 1+Σdp[j][4]; 直接由上面变形而来 dp[i][1] = dp[i][4] + min(dp[k][0]-dp[k][3]); 选一个儿子 需保证所有孙子被覆盖 即 dp[i][4] 然后要选出一个儿子 将他从0~3状态变为选了自己(由于dp[i][4]中他是3状态所以要减去一个dp[k][3]) 取这个差值最小的儿子 dp[i][2] = dp[i][3] + min(dp[k][1]-dp[k][2]); 选一个孙子 与上面类似 要保证所有儿子都被覆盖 即dp[i][3] 再将一个儿子从0~2状态变为0~1状态以保证覆盖他父节点 dp[i][3] = Σdp[j][2]; 保证所有儿子被覆盖 儿子的0~2状态均符合条件 dp[i][4] = Σdp[j][3]; 保证所有儿子的儿子被覆盖 儿子的0~3状态均符合条件 别问我为什么dp[i][1]和dp[1][2]用到后面的状态 因为你只需要在过程中记下那一坨min的值 把3,4处理完后再算1,2 另外由于数据特殊性 编号大的节点一定是编号小的节点的后代 所以递推顺序直接到着推就好了 代码:(神犇的代码稍作改动)
#include <bits/stdc++.h> using namespace std; typedef long long ll; #define inf 0x39393939 #define maxn 1010 bool G[maxn][maxn]; int dp[maxn][5]; int main() { int n; cin>>n; for(int i=2; i<=n; i++) { int x; cin>>x; G[x][i]=1; } for(int i=n; i>=1; i--) { int x1=inf,x2=inf; dp[i][0]=1; for(int j=1; j<=n; j++) if(G[i][j]) { dp[i][0]+=dp[j][4]; dp[i][3]+=dp[j][2]; dp[i][4]+=dp[j][3]; x1=min(x1,dp[j][0]-dp[j][3]); x2=min(x1,dp[j][1]-dp[j][2]); } dp[i][1]=dp[i][4]+x1; dp[i][2]=min(dp[i][3]+x2,min(dp[i][0],dp[i][1])); dp[i][3]=min(dp[i][2],dp[i][3]); dp[i][4]=min(dp[i][3],dp[i][4]); } cout<<dp[1][2]; return 0; }
//先用dfs每个节点到1号节点距离. //排序. //从距离最远,且未标记的节点搜索4步,并标记搜到的节点,每搜索一次答案加1. #include<iostream> #include<vector> #include<algorithm> using namespace std; int n, ans; bool b[1024], c[1024]; vector<int> v[1024]; pair<int,int> q[1024]; void dfs(int x,int d){//用dfs每个节点到1号节点距离 q[x]=make_pair(d,x); for(int i=0; i<v[x].size(); i++){ if(!b[v[x][i]]){ b[v[x][i]]=1; dfs(v[x][i],d+1); b[v[x][i]]=0; } } } void dfs4(int x,int d){//搜索四步,并标记 c[x]=1; if(d==0) return; for(int i=0; i<v[x].size(); i++){ if(!b[v[x][i]]){ b[v[x][i]]=1; dfs4(v[x][i],d-1); b[v[x][i]]=0; } } } int main(){ int i, x; cin >> n; for(i=2; i<=n; i++){ cin >> x; v[i].push_back(x); v[x].push_back(i); } b[1]=1; dfs(1,0); b[1]=0; sort(q,q+n+1); for(i=n; i>0; i--){ if(!c[q[i].second]){ c[q[i].second]=1; b[q[i].second]=1; dfs4(q[i].second,4); b[q[i].second]=0; ans++; } } cout << ans << endl; return 0; }
记住一句话:能实现这个目标的所有途径都给老子找出来,拿出来互相比较大小!!! 状态:dp[i][0]:选自己dp[i][1]:选了至少一个儿子dp[i][2]:选了至少一个孙子-----------------------------------这三种是覆盖了自己的dp[i][3]: 儿子孙子全部覆盖dp[i][4]:孙子全部覆盖-----------------------------------这两种并没有覆盖自己建议画一棵深度为3的完全二叉树直观观察状态 否则转移方程比较难懂
初始转移方程:dp[i][0] = 1+Σmin(dp[j][0...4]);要使选了根节点之后合法(整棵子树包括根节点被覆盖)必须使儿子的孙子全部覆盖 0~4状态满足 dp[i][1] = min( dp[k][0] + Σ(j != k)min(dp[j][0...3]) );要使选了一个儿子之后合法 由于儿子只可以覆盖到兄弟 所以孙子一定要全部被覆盖 即儿子的儿子一定覆盖 0~3满足 dp[i][2] = min( dp[k][1] + Σ(j != k)min(dp[j][0...2]) );使选了一个孙子之后合法 由于孙子最多只能覆盖到当前节点 所以儿子一定全部覆盖 即所有儿子本身要被覆盖 0~2满足 dp[i][3] = Σdp[j][0...2];要使儿子及孙子全部被覆盖 即儿子本身要被覆盖 0~2满足 dp[i][4] = Σdp[j][0...3]; 要使孙子全部被覆盖 即儿子的儿子要全部被覆盖 0~3满足::注意每种状态由儿子转移过来所以根的情况 要转化成对于儿子来说的情况 然后改进状态 因为每种转移方程至少有三种可能最后取其中较小的 故时间效率较低 令dp[i][k]表示min(dp[i][0],dp[i][1]....dp[i][k])且k>=2 因为上述转移方程最少都是0~2状态 那么转移方程就大幅度化简了:dp[i][0] = 1+Σdp[j][4];直接由上面变形而来 dp[i][1] = dp[i][4] + min(dp[k][0]-dp[k][3]);选一个儿子 需保证所有孙子被覆盖 即 dp[i][4] 然后要选出一个儿子 将他从0~3状态变为选了自己(由于dp[i][4]中他是3状态所以要减去一个dp[k][3]) 取这个差值最小的儿子dp[i][2] = dp[i][3] + min(dp[k][1]-dp[k][2]); 选一个孙子 与上面类似 要保证所有儿子都被覆盖 即dp[i][3] 再将一个儿子从0~2状态变为0~1状态以保证覆盖他父节点dp[i][3] = Σdp[j][2];保证所有儿子被覆盖 儿子的0~2状态均符合条件 dp[i][4] = Σdp[j][3];保证所有儿子的儿子被覆盖 儿子的0~3状态均符合条件
别问我为什么dp[i][1]和dp[1][2]用到后面的状态 因为你只需要在过程中记下那一坨min的值 把3,4处理完后再算1,2另外由于数据特殊性 编号大的节点一定是编号小的节点的后代 所以递推顺序直接到着推就好了代码:(神犇的代码稍作改动)