A. Parsa’s Humongous Tree(树上DP-互斥问题)
传送门:A. Parsa’s Humongous Tree
题意:给定一棵树,给每个节点在各自特定区间[li,ri]内选定一个权值,定义边的权值为 abs(val[u]-val[v])
求解整棵树的所有边的权值和的最大值。
首先根据贪心可得一定在所有节点取得最小或最大值的时候取得最优解,那么这道题就转化成了一个典型的树上DP互斥问题。
对于每个节点用0,1状态标记其取最小或最大值,最后从根节点逐层DP便可得到最优解。
#include<iostream>
#include<vector>
#include<algorithm>
#include<string>
#include<stack>
#include<vector>
#include<queue>
#include<cmath>
#include<map>
#define int long long
using namespace std;
const int maxn=2e5+5;
vector<int> G[maxn];
bool vis[maxn]={};
int l[maxn]={};
int r[maxn]={};
int color[maxn]={};
bool vis1[maxn]={};
int ans[maxn][2]={};
void dfs(int u){
for(auto v:G[u]){
if(vis[v])continue;
vis[v]=1;
dfs(v);
//状态转移方程,选取所有子树的最佳状态进行状态转移
ans[u][0]+=max(abs(l[u]-l[v])+ans[v][0],abs(l[u]-r[v])+ans[v][1]);
ans[u][1]+=max(abs(r[u]-l[v])+ans[v][0],abs(r[u]-r[v])+ans[v][1]);
}
return;
}
signed main(){
std::ios::sync_with_stdio(false);
int q;cin>>q;while(q--){
int n;cin>>n;
for(int i=1;i<=n;i++){
G[i].clear();
vis[i]=0;vis1[i]=0;
in[i]=0;color[i]=0;
ans[i][0]=ans[i][1]=0;
}
for(int i=1;i<=n;i++)cin>>l[i]>>r[i];
int m=n-1;
while(m--){
int u,v;cin>>u>>v;
G[u].push_back(v);
G[v].push_back(u);
}
vis[1]=1;
dfs(1);
cout<<max(ans[1][0],ans[1][1])<<endl;
}
return 0;
}
C. Trees of Tranquillity (DFS时间戳)
传送门 C. Trees of Tranquillity
题意:给定两棵树T1,T2,两棵树共享n个节点。对于两个节点u,v,如果两节点在T1中为父子关系但在T2中不为父子关系,则该两节点间存在一条边。求最大完全图所包涵的节点数。
先考虑T1,由于我们要求的是完全图,那么说明这个图中的所有节点都在T1树的一条边上,只有这样才能保证两两之间都含有路径。
再在这个基础上考虑T2,题目便可转化为,在T1中求一条路径,并删除该路径中的一些点,保证该路径中剩余的点两两之间均不存在父子关系,求可以保留节点数的最大值。
为了满足T2树的条件,我们需要用到DFS树中时间戳的知识,这里贴一张笔记,详情可以参考算法导论中讲解图论DFS的那一张(字丑莫怪)
可以看见,只要我们记录开始访问该节点的时间戳和结束访问该节点的时间戳,就很容易判断两个节点是否存在父子关系了(如果一个节点的区间包含了另一个节点的区间,那么说明它们存在父子关系)
那么该题的解法便一目了然了。首先,我们按照T2树跑一遍DFS记录每个节点的时间戳,随后我们再按T1树跑dfs,并开一个set记录所选节点的时间戳,如果遇到父子关系,根据贪心原理,我们将父节点删除,然后不停更新ans即可。(因为父节点的子树大于子节点的子树,故删除父节点,我们只是出现极限一换一的情况,如果删除子节点,那么我们可能要删除很多个子节点来满足父节点的合法性,这显然劣于删除父节点)
记得回溯,记得回溯,记得回溯。
#include<iostream>
#include<vector>
#include<algorithm>
#include<string>
#include<stack>
#include<vector>
#include<queue>
#include<cmath>
#include<set>
#include<map>
#define pii pair<int,int>
#define int long long
using namespace std;
const int mod=998244353;
const int maxn=1e6+5;
int dfn,ans=0,n;
vector<int>G1[maxn];
vector<int>G2[maxn];
int dfnl[maxn];
int dfnr[maxn];
void dfs(int u){
dfnl[u]=++dfn;//访问开始时间戳
for(auto v:G2[u]){
dfs(v);
}
dfnr[u]=++dfn;//访问结束时间戳
}
set<pii>S;
void Dfs(int u){
pii temp;temp.first =0,temp.second =0;
pii now;now.first=dfnl[u],now.second=dfnr[u];//now代表遍历到当前节点的时间戳
auto it=S.lower_bound(now);//寻找dfs序比自己晚的第一个节点
if(it!=S.begin()){//如果存在比自己dfs序早的节点,则判断该节点是否为自己的父节点
it--;
if(it->second>=dfnr[u]){//如果该结点的结束访问时间戳大于当前节点,说明该节点为自己的父节点
temp=*it;
S.erase(it);//擦除父节点
}
}
S.insert(now);//插入该节点
ans=max(ans,(int)S.size());
for(auto v:G1[u]){
Dfs(v);
}
if(temp.first)S.insert(temp);//回溯
S.erase(now);
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0);
int q;cin>>q;
while(q--){
cin>>n;
ans=dfn=0;
for(int i=1;i<=n;i++){
G1[i].clear();
G2[i].clear();
}
for(int i=2;i<=n;i++){
int x;cin>>x;
G1[x].push_back(i);
}
for(int i=2;i<=n;i++){
int x;cin>>x;
G2[x].push_back(i);
}
dfs(1);
Dfs(1);
cout<<ans<<endl;
}
return 0;
}