RBTREES - Editorial

PROBLEM LINK:

Practice
Contest

Setter: Vikas Pandey
Tester: Rahul Dugar
Editorialist: Ishmeet Singh Saggu

DIFFICULTY:

Easy-Medium

PREREQUISITES:

DFS, Greedy and Bipartite Graph

PROBLEM:

In a red-blue tree , each vertex is either red or blue, and adjacent vertices always have different colors.
You are given a tree with N vertices. The nodes of the tree are colored in either red or blue. You can swap the color of any two adjacent nodes. You have to make the given tree into a red-blue tree in the minimum number of swaps or state that it is impossible.

QUICK EXPLANATION:

  • As tree is a bipartite graph, so root the tree at node 1 and run a dfs to assign the parity(i.e. parity0 and parity1).
  • Minimum number of swaps required to swap the color of any 2 nodes without changing the color of nodes present on the path between them is equal to the distance between these two nodes.
  • There are 2 possible assignments.
    • Try to assign red color to nodes with parity0.
    • Try to assign red color to nodes with parity1.
  • Let us solve when we try to assign red color to nodes with parity0.
    • For a subtree of node X if all the nodes in the subtree have the proper color then it is optimal to not disturb it.
    • We will focus on two types of nodes.
      • Type1 - It is having a parity1 and red in color.
      • Type2 - It is having a parity0 and is blue in color.
    • Now we will run a dfs and for each node X we will maintain 2 parameters, excess and req.
      • excess - It is the count of nodes with Type1 present in its subtree and not yet swapped color with the node of Type2. And as we move up the dfs we will add it to our answer, where excess of node X is equal to the sum of excess of its child nodes and +1 if type(X) == Type1.
      • req - It is the count of nodes with Type2 present in its subtree and not yet swapped color with the node of Type2. And as we move up the dfs we will add it to our answer, req of node X is equal to the sum of req of its child nodes and +1 if type(X) == Type2.
    • Note! node X can have either a non-zero excess or a non-zero req or both be zero. Suppose we get excess = 5 and req = 3 by using above step, it will be optimal to swap 3 nodes from excess to fill the 3 nodes of req and their cost of swaps we have already added to our answer as we moved up the dfs. So new excess = 2 and req = 0 of subtree of node X. We will add this updated excess and req to our answer when we move up the dfs.
  • Think how to know if an assignment is possible of not
  • Similarly, We will do it for the other case in which we try to assign red color to nodes with parity1 and if the assignment is possible then we will take the minimum of these two, else we will take the case which is possible. And if both the cases are not possible then the answer is impossible and we will print -1.

EXPLANATION:

A tree is a bipartite graph, let us refer to it’s 2 parities(or 2 disjoint sets) as parity0 and parity1. So first run a dfs to assign parity to the nodes. Now it can be formed to a red-blue tree by swapping nodes, given that the count of red nodes is equal to either to the count of nodes with parity0 or parity1(because in that case count of blue nodes will be equal to the other parity). Otherwise, the answer is impossible.

Also to make the problem simpler we will only focus on assigning red color nodes because if they are assigned correctly all the blue color nodes will automatically be assigned correctly.

We will take two cases :

  1. When we are trying to assign the red color to the nodes with parity0 if the count of red color nodes is equal to the count of nodes with parity0.
  2. When we are trying to assign the red color to the nodes with parity1 if the count of red color nodes is equal to the count of nodes with parity1.

In explanation, I will explain the first case as the second case can be done similarly. So let’s start.

One observation is that you can swap the color of any two nodes in the tree without changing the color of nodes that are present on the path between them. And the minimum number of swaps required to do this is equal to the distance between these 2 nodes. I will explain it with some example:-

Example 1. We want to swap the color of node 1 to node 3 without changing the color of the in-between nodes.
an_image_red_swaps_1_png
We can do so by swapping nodes 2 and 3 first and then node 1 and 2. Total No. of swaps = 2.

Example 2. We want to swap the color of node 1 to node 4.
an_image_red_swaps_2_png
We can do so by swapping nodes 3 and 4 first, then node 2 and 3 and then node 1 and 2. Total No. of swaps = 3.

Example 3. We want to swap the color of node 1 to node 5.
an_image_red_swaps_3_png
We can do so by swapping nodes 3 and 4 first, then node 2 and 3, then node 1 and 2 and finally node 4 and 5. Total No. of swaps = 4.

By this observation, we can say that we can still change the color of other nodes without disturbing the nodes lying between the path. So it is optimal to not to disturb the nodes that have parity0 and red color or the node with parity1 and blue color as we can still freely swap the colors of the nodes.

  • First, let us root the tree at node 1.
  • For a subtree of node X if all the nodes in the subtree have the proper color then it is optimal to not disturb it.

Now consider two types of nodes :

  • Type1 - It is having a parity1 and red in color.
  • Type2 - It is having a parity0 and is blue in color.

an_image_separater_png

Some Initial Thoughts
  • Suppose we encounter a node with Type1 and it is either a leaf node or all it’s child subtrees are properly assigned. Then we have to pass it up the edge(joining this node and its parent) adding one to the answer. As the only option is to pass the red color up as all its child subtree is properly assigned.
  • Similarly, if we encounter a node with Type2 and it is either a leaf node or all it’s child subtrees are properly assigned. Then it will require a red color, and its only option to get it through the edge joining it with the parent so it will also add one to our answer.

Now we will run a dfs and for each node X we will maintain 2 parameters, excess and req.

  • excess - It is the count of nodes with Type1 present in its subtree and not yet swapped color with the node of Type2. And as we move up the dfs (exiting the dfs function of node X can be visualized as traveling up or traveling to its parent and is referred to as moving up the dfs) we will add it to our answer. As the only way is to send it above passing through the edge connecting node X and it’s parent, as all other nodes in the subtree of X are properly assigned.
  • req - It is the count of nodes with Type2 present in its subtree and not yet swapped color with with the node of Type1. And as we move up the dfs we will add it to our answer. As the only way is to get it from above passing through the edge connecting node X and it’s parent, as all other nodes in the subtree of X are properly assigned.
  • excess of node X is equal to the sum of excess of its child nodes and +1 if its Type1.
excess[X] = 0;
if(type(X) == Type1) {
	excess[X] ++;
}
for(auto child : graph[X]) {
	if(child == parent) continue;
	excess[X] += excess[child];
}
  • req of node X is equal to the sum of req of its child nodes and +1 if its Type2.
req[X] = 0;
if(type(X) == Type2) {
	req[X] ++;
}
for(auto child : graph[X]) {
	if(child == parent) continue;
	req[X] += req[child];
}
  • Note! node X can have either a non-zero excess or a non-zero req or both be zero(in case the subtree is properly assigned). Why? because after computing excess and req for node X by using above steps suppose we get excess = 5 and req = 3, so instead of keeping it as same, what will be the optimal way to fulfill these requirements :thinking:, Yep we can swap 3 nodes from excess to fill the 3 nodes of req and there cost of swaps we have already added to our answer as we moved up the dfs. So new excess = 2 and req = 0 and this will be the excess and req of subtree of node X. So after computing excess and req we will update them as below and after that we will add it to our answer as we move up.
temp = min(excess[X], req[X]);
excess[X] -= temp;
req[X] -= temp;
  • So if excess = req it means the subtree can be properly assigned by only using nodes in its own subtree as excess and req will become 0. So if excess = req for node 1(root) then we can consider the answer which we computed. Else it is not possible to make the red-blue tree in the current case(You can use this condition to reduce your implementation :wink:).

Similarly, We will do it for the other case in which we try to assign red color to nodes with parity1 and if the assignment is possible then we will take the minimum of these two, else we will take the case which is possible. And if both the cases are not possible then the answer is impossible and we will print -1.

TIME COMPLEXITY:

  • We are running one dfs to assign parity to the nodes and 2 dfs for both the cases of the assignment. So total time complexity per test case is O(N).

SOLUTIONS:

Setter's Solution
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define ff first
#define ss second
#define endl "\n"
 
vector<vector<int>> adj;
string s;
long long ans;
 
int dfs(int root, int par, int color)
{
    int curr=0, c=s[root]-'0';
    for(auto x:adj[root])
        if(x!=par)
            curr+=dfs(x, root, color^1);
    if(c^color)
        curr+=(c==0)?1:-1;
    ans+=abs(curr);
    return curr;
}
 
void solve()
{
    long long n, fans=LLONG_MAX;
    cin>>n;
    adj.clear();
    adj.resize(n);
    for(int i=1; i<n; i++)
    {
        int u, v;
        cin>>u>>v;
        u--, v--;
        adj[u].pb(v);
        adj[v].pb(u);
    }
    cin>>s;
    for(int rcolor=0, tmp=dfs(0, 0, rcolor); (rcolor<2); rcolor++, ans=0, tmp=dfs(0, 0, rcolor))
        fans=tmp==0?min(fans, ans):fans;
    cout<<((fans==LLONG_MAX)?-1:fans)<<endl;
}
 
int32_t main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    int t=1;
    cin>>t;
    while(t--)
        ans=0, solve();
    return 0;
} 
Tester's Solution
#include <bits/stdc++.h>
using namespace std;
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/rope>
using namespace __gnu_pbds;
using namespace __gnu_cxx;
#ifndef rd
#define trace(...)
#define endl '\n'
#endif
#define pb push_back
#define fi first
#define se second
#define int long long
typedef long long ll;
typedef long double f80;
#define double long double
#define pii pair<int,int>
#define pll pair<ll,ll>
#define sz(x) ((long long)x.size())
#define fr(a,b,c) for(int a=b; a<=c; a++)
#define rep(a,b,c) for(int a=b; a<c; a++)
#define trav(a,x) for(auto &a:x)
#define all(con) con.begin(),con.end()
const ll infl=0x3f3f3f3f3f3f3f3fLL;
const int infi=0x3f3f3f3f;
//const int mod=998244353;
const int mod=1000000007;
typedef vector<int> vi;
typedef vector<ll> vl;
 
typedef tree<int, null_type, less<int>, rb_tree_tag, tree_order_statistics_node_update> oset;
auto clk=clock();
mt19937_64 rang(chrono::high_resolution_clock::now().time_since_epoch().count());
int rng(int lim) {
	uniform_int_distribution<int> uid(0,lim-1);
	return uid(rang);
}
 
long long readInt(long long l,long long r,char endd){
	long long x=0;
	int cnt=0;
	int fi=-1;
	bool is_neg=false;
	while(true){
		char g=getchar();
		if(g=='-'){
			assert(fi==-1);
			is_neg=true;
			continue;
		}
		if('0'<=g && g<='9'){
			x*=10;
			x+=g-'0';
			if(cnt==0){
				fi=g-'0';
			}
			cnt++;
			assert(fi!=0 || cnt==1);
			assert(fi!=0 || is_neg==false);
 
			assert(!(cnt>19 || ( cnt==19 && fi>1) ));
		} else if(g==endd){
			if(is_neg){
				x= -x;
			}
			assert(l<=x && x<=r);
			return x;
		} else {
			assert(false);
		}
	}
}
string readString(int l,int r,char endd){
	string ret="";
	int cnt=0;
	while(true){
		char g=getchar();
		assert(g!=-1);
		if(g==endd){
			break;
		}
		cnt++;
		ret+=g;
	}
	assert(l<=cnt && cnt<=r);
	return ret;
}
long long readIntSp(long long l,long long r){
	return readInt(l,r,' ');
}
long long readIntLn(long long l,long long r){
	return readInt(l,r,'\n');
}
string readStringLn(int l,int r){
	return readString(l,r,'\n');
}
string readStringSp(int l,int r){
	return readString(l,r,' ');
}
 
 
vi gra[100005];
int dsu[100005];
int fpar(int u) {
	return (dsu[u]<0)?u:dsu[u]=fpar(dsu[u]);
}
void merge(int u, int v) {
	if((u=fpar(u))!=(v=fpar(v))) {
		if(dsu[u]>dsu[v])
			swap(u,v);
		dsu[u]+=dsu[v];
		dsu[v]=u;
	}
}
 
int intend[100005],act[100005];
string s;
int dfs(int fr, int at, int bol) {
	if(s[at-1]=='0')
		act[at]++;
	if(bol)
		intend[at]++;
	int res=0;
	for(int i:gra[at]) {
		if(i!=fr) {
			res+=dfs(at,i,bol^1);
			intend[at]+=intend[i];
			act[at]+=act[i];
		}
	}
	res+=abs(intend[at]-act[at]);
	return res;
}
int sum_n=0;
void solve() {
	int n=readIntLn(1,100000);
	sum_n+=n;
	memset(dsu,-1,sizeof(int)*(n+5));
	fr(i,1,n)
		gra[i].clear();
	fr(i,2,n) {
		int u=readIntSp(1,n),v=readIntLn(1,n);
		assert(u!=v);
		merge(u,v);
		gra[u].pb(v);
		gra[v].pb(u);
	}
	assert(dsu[fpar(1)]==-n);
	s=readStringLn(n, n);
	for(char i:s)
		assert(i=='0'||i=='1');
	int ans=infl;
	memset(intend,0,sizeof(int)*(n+5));
	memset(act,0,sizeof(int)*(n+5));
	int temp=dfs(1,1,0);
	if(intend[1]==act[1])
		ans=min(ans,temp);
	memset(intend,0,sizeof(int)*(n+5));
	memset(act,0,sizeof(int)*(n+5));
	temp=dfs(1,1,1);
	if(intend[1]==act[1])
		ans=min(ans,temp);
	if(ans==infl)
		ans=-1;
	cout<<ans<<endl;
}
signed main() {
	ios_base::sync_with_stdio(0),cin.tie(0);
	srand(chrono::high_resolution_clock::now().time_since_epoch().count());
	cout<<fixed<<setprecision(8);
	int t=readIntLn(1,100);
	while(t--)
		solve();
	assert(1<=sum_n&&sum_n<=1000000);
	assert(getchar()==EOF);
	return 0;
}
Editorialist's Solution
#include <bits/stdc++.h>
using namespace std;

const long long INF = 2e18;

int N;
long long ans;
vector<vector<int>> graph;
vector<int> parity, color, excess, req;

// below function assign parity to the node, NOTE, tree is a bipartite graph.
void assignParity(int node, int parent, int t) {
	parity[node] = t;
	for(auto to : graph[node]) {
		if(to == parent) continue;
		assignParity(to, node, t^1);
	}
}

void dfs(int node, int parent, int prty) {
	excess[node] = (parity[node] == prty);
	req[node] = (color[node] == 0);
	for(auto to : graph[node]) {
		if(to == parent) continue;
		dfs(to, node, prty);
		excess[node] += excess[to];
		req[node] += req[to];
	}
	int temp = min(excess[node], req[node]);
	excess[node] -= temp;
	req[node] -= temp;
	ans += excess[node] + req[node]; // if the number of unsatisfied nodes is non-zero then it will contribute to our answer by swapping along the edge (node --- parent).
	// exiting process of the dfs can be visualized as moving up an edge(node --- parent)
}

void solveTestCase() {
	cin >> N;
	graph.clear();
	graph.resize(N+1);
	parity.assign(N+1, 0);
	color.assign(N+1, 0);
	excess.assign(N+1, 0);
	req.assign(N+1, 0);
	for(int i = 0; i < N-1; i ++) {
		int a, b;
		cin >> a >> b;
		graph[a].push_back(b);
		graph[b].push_back(a);
	}
	for(int i = 1; i <= N; i ++) {
		char in; cin >> in;
		color[i] = (in-'0'); // let 0 be red color and 1 be blue color.
	}
	assignParity(1, -1, 0); // we will root the tree at node 1.
	long long res = INF;
	for(int i = 0; i < 2; i ++) {
		ans = 0;
		dfs(1, -1, i); // trying to assign color 0 to nodes with parity i, in minimum number of steps. 
		if(excess[1] == req[1]) { // If we have successfully assigned the nodes.
			res = min(ans, res);
		}
	}
	if(res == INF) cout << -1 << endl;
	else cout << res << endl;
}

int main() {
	ios_base::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);

	int testCase;
	cin >> testCase;
	for(int i = 1; i <= testCase; i ++) {
		solveTestCase();
	}

	return 0;
}

Feel free to share your approach. In case of any doubt or anything is unclear please ask it in the comment section. Any suggestions are welcomed. :smile:

22 Likes

Was looking for a better editorial than the one at csacademy . Setter’s solution looks simple but the approach explained in the editorial seems totally unrelated . Can someone explain the setter’s approach.

11 Likes

they both are same, just shortened.

1 Like

You could have copied the editorial as well from here: CS Academy
Why bother writing it?

32 Likes

can some one help me to understand the algorithm ?

1 Like

:grinning: :grinning: :grinning: :rofl:

1 Like

exactly

1 Like

Is it a typo ?
req[X] = 0;
if(type(X) == Type2) {
excess[X] ++; // I think this should be “req[x]++;”
}

1 Like

if we mark all nodes at odd depth with 0 and even depth with 1 or vice - versa
and then optimise/calculate the cost of converting the given string into the form
011…11100…0011…1111 or 1000…011…10…0
will this work ???

1 Like

fixed, thanks for mentioning it.

1 Like

I did the exact same thing but I was getting TLE. Can anyone tell me whats wrong?
TLE Solution link: CodeChef: Practical coding for everyone
I used donor and taker in place of excess and req variables. And ignore the commented lines ( :smiley: ) And my dfs function name is solve

1 Like

But since there is a restriction about which nodes can be swapped with which ones (adjacent nodes in the tree will not always be adjacent in the string), how would you approach for that?

2 Likes

It doesn’t seem intentional to me. The problem statement is fairly simple, so it’s easy to think of the same problem. But yeah, they should’ve researched a bit more and checked if the problem already existed somewhere else.

6 Likes

We did not know that problem existed. We did decent amount of check but could not find. As someone suggested, I should have checked more given the problem statement is so simple and natural.

7 Likes

i did the same thing followed by finding distance between the positions where we need 1(pos1) which is somewhere else(pos2) in the original string but got WA.
is this wrong??

exactly.How can this work is only adjacent swap allowed.

What would be the difficulty level for this question for somebody who knows basics techniques in tress and graphs, will he be able to solve it? i haven’t worked on trees yet.

1 Like

Sir can u please help
why am i getting TLE in my code despite using the same logic as yours?
Submission: CodeChef: Practical coding for everyone

2 Likes

If you know how to do a dfs, then you can do it.
You can refer to this solution :

4 Likes

Does not matter only 57 participants were able to solve it. :rofl: :rofl:

2 Likes