题目链接:https://codeforces.com/contest/1263/problem/F
题目大意:
有两棵树,都以1为根节点,两棵树的叶子数量相同,且都分别连接一个电机,若电机存在到达任意一棵树的根节点的路径,则表示该电机可用,问最多删除多少条边仍能保持所有电机可用。
题目思路:
qsc讲解视频传送门:传送门
首先可以发现一个事情,那就是这是一棵树,如果让某个节点报废的话,那么这个节点一定会导致几个电机报废,也就是这个节点拥有的叶子所掌管的电机。同时树有一个很好的性质,就是删边可以转化为删点,每个边都转化为它指向的那个点,只有根节点没有这个待遇为0。那么如果删除一个点,那么它覆盖的电机就会得不到这棵树的保护,同时删除的边数就是点的个数(该点不是根的情况下)。那么,第一步的预处理就显而易见了!
l
[
_
]
[
i
]
l[\_][i]
l[_][i]表示对于第_棵树的第i个节点,控制的最左电机是哪个,r相反,这样的话通过dfs就能获得每个节点能控制的电机是那几台,
v
a
l
[
l
]
[
r
]
val[l][r]
val[l][r]表示某一棵树l~r的电机得不到保护能删除的最多的边(因为只有一棵树这个区间的电机凉了,所以它们可以交给另一棵树帮忙顶住),val的更新刚才说过,边的数量可以转换为子树大小,就是注意根节点为0,所以只有非根节点,
s
z
[
_
]
[
u
]
sz[\_][u]
sz[_][u]才初始化为1。
然后就是dp阶段,
d
p
[
i
]
dp[i]
dp[i]表示1~i最多能删几条边。这个dp转移就是n^2枚举区间,每个区间的val就表示这个区间的电机少一棵树庇佑最多能删除多少边,那么
d
p
[
i
−
1
]
+
v
a
l
[
i
]
[
j
]
dp[i-1]+val[i][j]
dp[i−1]+val[i][j]就是1 ~ i-1最多能删除的边数加上i~j区间内最多能删除多少边,就是
d
p
[
i
]
dp[i]
dp[i]能删除多少边。
感觉这个DP非常巧妙,结合树就更有意思了,即使现在明白了它的做法,还是觉得这个思路非常难想到,希望以后能独立做出这种DP。
以下是代码:
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define per(i,a,b) for(int i=a;i>=b;i--)
#define ll long long
const int MAXN = 2e3+5;
int n,a,x,l[2][MAXN],r[2][MAXN],sz[2][MAXN],val[MAXN][MAXN];
vector<int>v[2][MAXN];
int dp[MAXN];
void dfs(int _,int u){
int len=v[_][u].size();
if(u!=1)sz[_][u]=1;
rep(i,0,len-1){
int y=v[_][u][i];
dfs(_,y);
sz[_][u]+=sz[_][y];
l[_][u]=min(l[_][u],l[_][y]);
r[_][u]=max(r[_][u],r[_][y]);
}
val[l[_][u]][r[_][u]]=max(val[l[_][u]][r[_][u]],sz[_][u]);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
while(cin>>n){
memset(dp,0,sizeof(dp));
memset(sz,0,sizeof(sz));
memset(val,0,sizeof(val));
rep(_,0,1){
cin>>a;
rep(i,1,a)l[_][i]=n+1,r[_][i]=0;
rep(i,1,a)v[_][i].clear();
rep(i,2,a){
cin>>x;
v[_][x].push_back(i);
}
rep(i,1,n){
cin>>x;
l[_][x]=r[_][x]=i;
}
}
rep(_,0,1)dfs(_,1);
rep(i,1,n){
rep(j,i,n){
dp[j]=max(dp[j],dp[i-1]+val[i][j]);
}
}
cout<<dp[n]<<endl;
}
}