Abendsen assigned a mission to Juliana. In this mission, Juliana has a rooted tree with n vertices. Vertex number 1 is the root of this tree. Each vertex can be either black or white. At first, all vertices are white. Juliana is asked to process q queries. Each query is one of three types:

  1. If vertex v is white, mark it as black; otherwise, perform this operation on all direct sons of v instead. 
  2. Mark all vertices in the subtree of v (including v) as white. 
  3. Find the color of the i-th vertex. 

<image> An example of operation "1 1" (corresponds to the first example test). The vertices 1 and 2 are already black, so the operation goes to their sons instead.

Can you help Juliana to process all these queries?

Input

The first line contains two integers n and q (2≤ n≤ 10^5, 1≤ q≤ 10^5) — the number of vertices and the number of queries.

The second line contains n-1 integers p_2, p_3, …, p_n (1≤ p_i<i), where p_i means that there is an edge between vertices i and p_i. 

Each of the next q lines contains two integers t_i and v_i (1≤ t_i≤ 3, 1≤ v_i≤ n) — the type of the i-th query and the vertex of the i-th query.

It is guaranteed that the given graph is a tree.

Output

For each query of type 3, print "black" if the vertex is black; otherwise, print "white".

Examples

Input

8 10
1 2 1 2 5 4 5
1 2
3 2
3 1
1 1
1 1
3 5
3 7
3 4
2 2
3 5


Output

black
white
black
white
black
white


Input

8 11
1 1 2 3 3 6 6
1 1
1 1
1 3
3 2
3 4
3 6
3 7
2 3
1 6
3 7
3 6


Output

black
white
black
white
white
black

Note

The first example is shown on the picture below.

<image>

The second example is shown on the picture below.

<image>
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.StringTokenizer;

public class DivCmb_502G implements Runnable {

	public static void main(String[] args) throws IOException {
		new Thread(null, new DivCmb_502G(), "yay", 1 << 26).start();
	}

	public void run() {
		try {
			BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
			PrintWriter printer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
			StringTokenizer inputData = new StringTokenizer(reader.readLine());
			nV = Integer.parseInt(inputData.nextToken());
			int nQ = Integer.parseInt(inputData.nextToken());
			root = 1;

			chldn = new ArrayList[nV + 1];
			for (int i = 1; i <= nV; i++) {
				chldn[i] = new ArrayList<>();
			}
			par = new int[nV + 1];
			par[root] = -1;

			inputData = new StringTokenizer(reader.readLine());
			for (int i = 2; i <= nV; i++) {
				int p = Integer.parseInt(inputData.nextToken());
				par[i] = p;
				chldn[p].add(i);
			}

			init();

			for (int cQ = 1; cQ <= nQ; cQ++) {
				inputData = new StringTokenizer(reader.readLine());
				int t = Integer.parseInt(inputData.nextToken());
				int cV = Integer.parseInt(inputData.nextToken());
				if (t == 1) {
					mark(cV);
				} else if (t == 2) {
					st_Clear(cV);
				} else {
					Data cData = rP_Query(cV);
					if (cData.def > 0) {
						printer.println("black");
					} else {
						printer.println("white");
					}
				}
			}
			printer.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	class Data {
		int gap;
		int def;

		Data(int gap, int def) {
			this.gap = gap;
			this.def = def;
		}

		public String toString() {
			return "(gap: " + gap + ", def: " + def + ")";
		}

	}

	Data merge(Data d1, Data d2) {
		if (d1 == null) {
			return d2;
		}
		if (d2 == null) {
			return d1;
		}
		if (d1.def - 1 <= d2.gap) {
			return new Data(d1.gap + d2.gap - (d1.def - 1), d2.def);
		} else {
			return new Data(d1.gap, d2.def + d1.def - 1 - d2.gap);
		}
	}

	int nV;

	int root;

	ArrayList<Integer>[] chldn;
	int[] par;

	int[] heavyC;
	int[] chainS;

	LPSegTree tree;
	int[] tInd;
	int[] stEnd;

	// returns vCount
	int initDFS(int cV) {
		int cDeg = 1;
		int mADeg = 0;
		heavyC[cV] = -1;

		for (int aV : chldn[cV]) {
			int aDeg = initDFS(aV);
			if (aDeg > mADeg) {
				mADeg = aDeg;
				heavyC[cV] = aV;
			}
			cDeg += aDeg;
		}
		return cDeg;
	}

	int nTInd = 1;

	void cODFS(int cV) {
		tInd[cV] = nTInd++;
		int cHeavyC = heavyC[cV];

		if (cHeavyC != -1) {
			chainS[cHeavyC] = chainS[cV];
			cODFS(cHeavyC);
		}

		for (int aV : chldn[cV]) {
			if (aV != cHeavyC) {
				chainS[aV] = aV;
				cODFS(aV);
			}
		}
		stEnd[cV] = nTInd - 1;
	}

	void init() {
		heavyC = new int[nV + 1];
		chainS = new int[nV + 1];

		tree = new LPSegTree(nV);
		tInd = new int[nV + 1];
		stEnd = new int[nV + 1];

		initDFS(root);

		chainS[root] = root;
		cODFS(root);
	}

	Data rP_Query(int cV) {
		Data tData = null;
		while (cV != -1) {
			tData = merge(tree.query(tInd[chainS[cV]], tInd[cV]), tData);
			cV = par[chainS[cV]];
		}
		return tData;
	}

	void st_Clear(int cV) {
		tree.clear(tInd[cV], stEnd[cV]);
		if (cV != root) {
			Data pData = rP_Query(par[cV]);
			tree.update(tInd[cV], new Data(pData.def == 0 ? 0 : pData.def - 1, 0));
		}
	}

	void mark(int cV) {
		Data pVal = tree.query(tInd[cV], tInd[cV]);
		tree.update(tInd[cV], new Data(pVal.gap, pVal.def + 1));
	}

	class LPSegTree {
		int nE;
		Data[] tree;
		boolean[] lazy;

		LPSegTree(int nE) {
			this.nE = nE;
			tree = new Data[nE * 4];
			lazy = new boolean[nE * 4];
			init(1, 1, nE);
		}

		void init(int nI, int cL, int cR) {
			if (cL == cR) {
				tree[nI] = new Data(0, 0);
			} else {
				int mid = (cL + cR) >> 1;
				init(nI * 2, cL, mid);
				init(nI * 2 + 1, mid + 1, cR);
				tree[nI] = merge(tree[nI * 2], tree[nI * 2 + 1]);
			}
		}

		Data query(int qL, int qR) {
			return query(1, 1, nE, qL, qR);
		}

		Data query(int nI, int cL, int cR, int qL, int qR) {
			if (cR < qL || qR < cL) {
				return null;
			}

			pLazy(nI, cL, cR);

			if (qL <= cL && cR <= qR) {
				return tree[nI];
			}

			int mid = (cL + cR) >> 1;

			Data leftQ = query(nI * 2, cL, mid, qL, qR);
			Data rightQ = query(nI * 2 + 1, mid + 1, cR, qL, qR);

			return merge(leftQ, rightQ);
		}

		void update(int uI, Data nData) {
			update(1, 1, nE, uI, nData);
		}

		void update(int nI, int cL, int cR, int uI, Data nData) {
			pLazy(nI, cL, cR);

			if (cL == cR) {
				tree[nI] = nData;
			} else {
				int mid = (cL + cR) >> 1;
				if (uI <= mid) {
					update(nI * 2, cL, mid, uI, nData);
					pLazy(nI * 2 + 1, mid + 1, cR);
				} else {
					update(nI * 2 + 1, mid + 1, cR, uI, nData);
					pLazy(nI * 2, cL, mid);
				}
				tree[nI] = merge(tree[nI * 2], tree[nI * 2 + 1]);
			}
		}

		void clear(int uL, int uR) {
			clear(1, 1, nE, uL, uR);
		}

		void clear(int nI, int cL, int cR, int uL, int uR) {
			// current node needs to have no lazy before returning because it will be used to compute tree[par]
			pLazy(nI, cL, cR);

			if (cR < uL || uR < cL) {
				return;
			}

			if (uL <= cL && cR <= uR) {
				tree[nI] = new Data(cR - cL, 0);
				if (cL != cR) {
					lazy[nI * 2] = true;
					lazy[nI * 2 + 1] = true;
				}
				return;
			}

			int mid = (cL + cR) >> 1;

			clear(nI * 2, cL, mid, uL, uR);
			clear(nI * 2 + 1, mid + 1, cR, uL, uR);
			tree[nI] = merge(tree[nI * 2], tree[nI * 2 + 1]);
		}

		void pLazy(int nI, int cL, int cR) {
			if (lazy[nI]) {
				tree[nI] = new Data(cR - cL, 0);

				if (cL != cR) {
					lazy[nI * 2] = true;
					lazy[nI * 2 + 1] = true;
				}
				lazy[nI] = false;
			}
		}
	}
}