Codeforces 1363E - Tree Shuffling (思维/dfs/记忆化)
Posted stelayuri
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Codeforces 1363E - Tree Shuffling (思维/dfs/记忆化)相关的知识,希望对你有一定的参考价值。
Codeforces Round #646 (Div. 2) - E. Tree Shuffling
题意
有一棵 n 个节点 n-1 条边的树,以节点 1 为根节点
每个节点 i 拥有价值 ai 和一个初始值 bi ,希望最终将这个值从 bi 变成 ci
可以执行无限次操作,每次操作需要有两个值 u k
表示从以节点 u 为根的子树中选出 k 个节点随意交换它们的 bi
其中 k 至少为 1 ,最多为以 u 为根的子树的节点个数
此时的花费为 k×au
如果存在操作方案,求出最小花费,不存在输出 -1
限制
Time limit per test: 2 seconds
Memory limit per test: 256 megabytes
- 1≤n≤2?105
- 1≤ai≤109 , 0≤bi,ci≤1
- 1≤u,v≤n, u≠v
解题思路
对于不存在的情况
如果 b 数组中 1 的个数与 c 数组中的个数对不上,则无法通过交换将每个节点从状态 b 变成状态 c
容易想到,我们应该从花费最小的节点开始,以最优方案去交换这个节点的子树
所以首先根据花费来将节点进行排序,然后开始处理子树
对于子树的搜索,用双向存图方案的话需要先求出每个节点的深度dep,然后根据深度大小判断是否为子树的节点;用单向存图方案不需要
处理子树时,需要遍历子树的所有节点——
如果某个节点 i 的 bi 与 ci 相同,则无需进行交换
如果不同,则肯定是 1->0 或者 0->1 的情况,使用 cntb 和 cntc 来储存这两种情况
处理完节点 i 的子树后,
cntb 则表示子树中尚未交换但需要交换的节点需要 1->0 的数量
cntc 则表示子树中尚未交换但需要交换的节点需要 0->1 的数量
可以发现,如果选择了两个节点,一个是 0->1 ,一个是 1->0
那么让他们互相交换原来的值就可以满足题意
此时对应的是从 cntb 中取出 1,从 cntc 中取出 1,凑成一对
所以总共可以执行交换的节点对数为 min(cntb,cntc) ,数量为 2*min(cntb,cntc)
花费则为 ai * 2 * min(cntb,cntc)
但需要注意,可能此时会出现多出来一些 0->1 或者 1->0 的节点
所以需要再引入一个pair数组 les ,表示某个节点被访问过后还剩余的未处理的节点数,之后如果再次搜索到该节点时直接取 les 数组的值即可,同时也不用再返回去修改子树的值,做到记忆化搜索
因为有了les数组,所以如果按照花费从小到大排序后遍历
遍历过程中,如果某个节点已经被访问过,则不需要处理
如果没有访问过,搜索这个节点的子树,但搜索过程中如果遇到了访问过的节点,不能直接跳过
此时需要注意将访问过的节点的 les 数组值 加到 cntb 和 cntc 中
完整程序
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
struct node
{
ll a;
int id;
bool operator < (const node& x) const
{
return a<x.a; //根据花费从小到大排序
}
}arr[200050];
int b[200050],c[200050];
vector<int> G[200050]; //存图
bool vis2[200050];
int dep[200050]; //节点深度
void dfs2(int st,int d) //dfs2求每个节点的深度
{
dep[st]=d;
vis2[st]=true;
for(int it:G[st])
{
if(!vis2[it])
dfs2(it,d+1);
}
}
bool vis[200050];
P les[200050];
/*
(只有当i已经被访问过时,即vis[i]==true时)
first表示处理完节点i的子树后,在b数列中剩余的1的数量
second表示处理完节点i的子树后,在c数列中剩余的1的数量
*/
int cntb,cntc;
void dfs(int p)
{
if(vis[p]) //如果节点p已经被访问过
{
cntb+=les[p].first; //直接取尚未处理的种类数量加到cntb和cntc内即可
cntc+=les[p].second;
les[p].first=les[p].second=0; //置零
return;
}
vis[p]=true;
for(int it:G[p]) //搜索节点p的子树
{
if(!vis[it]) //如果没有访问过,就继续深搜
dfs(it);
else //如果已经被访问过了
{
cntb+=les[it].first; //直接取尚未处理的种类数量加到cntb和cntc内即可
cntc+=les[it].second;
les[it].first=les[it].second=0; //置零
}
}
if(b[p]!=c[p]) //判断节点p自己
{
if(b[p]==1)
cntb++;
else
cntc++;
}
}
void solve()
{
int n,tmp[2]={0,0},u,v;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>arr[i].a>>b[i]>>c[i];
arr[i].id=i;
if(b[i]==1)
tmp[0]++;
if(c[i]==1)
tmp[1]++;
}
for(int i=1;i<n;i++)
{
cin>>u>>v;
G[u].emplace_back(v);
G[v].emplace_back(u);
}
if(tmp[0]!=tmp[1]) //如果b中1的个数与c中1的个数不同,说明无法通过交换得到c的状态
{
cout<<"-1
";
return;
}
sort(arr+1,arr+n+1); //按照花费来排序
dfs2(1,1); //求出每个节点的深度,1为根
ll ans=0;
for(int i=1;i<=n;i++) //从最小花费开始
{
int cur=arr[i].id;
if(!vis[cur]) //只要这个节点没被访问过,才需要计算
{
vis[cur]=true;
les[cur]=P(0,0);
cntb=cntc=0;
for(int it:G[cur]) //从与cur相邻的节点中寻找
if(dep[it]>dep[cur]) //如果深度比cur大,说明是cur的子树
dfs(it); //从子树中深搜
if(b[cur]!=c[cur]) //判断cur自己
{
if(b[cur]==1)
cntb++;
else
cntc++;
}
les[cur].first+=cntb;
les[cur].second+=cntc;
int d=min(les[cur].first,les[cur].second); //取较小的值,然后就能交换2*d个节点
ans+=arr[i].a*d*2; //需要的花费
les[cur].first-=d; //剩余未被处理的数量
les[cur].second-=d;
}
}
cout<<ans<<‘
‘;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
以上是关于Codeforces 1363E - Tree Shuffling (思维/dfs/记忆化)的主要内容,如果未能解决你的问题,请参考以下文章
Codeforces 1363E - Tree Shuffling (思维/dfs/记忆化)
Codeforces 1363E - Tree Shuffling (思维/dfs/记忆化)
CF1363E Tree Shuffling(贪心+树上乱搞)
Codeforces Round #722 (Div. 2) C. Parsa's Humongous Tree(树形DP)
CodeForces 723F st-Spanning Tree
Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths CodeForces - 741Ddsu on tree+异或