【CSP-S2019】D1T1 格雷碼 & D1T2 括號樹 題解

CSP-S2019 D1T1 格雷碼

題目

題目描述

通常,人們習慣將所有 nn 位二進制串按照字典序排列,例如所有 22 位二進制串按字典序從小到大排列爲:0000010111111010

格雷碼(Gray Code)是一種特殊的 nn 位二進制串排列法,它要求相鄰的兩個二進制串間恰好有一位不同,特別地,第一個串與最後一個串也算作相鄰。

所有 2 位二進制串按格雷碼排列的一個例子爲:0000010111111010

nn 位格雷碼不止一種,下面給出其中一種格雷碼的生成算法:

  1. 1 位格雷碼由兩個 11 位二進制串組成,順序爲:0011
  2. n+1n + 1 位格雷碼的前 2n2^n 個二進制串,可以由依此算法生成的 nn 位格雷碼(總共 2n2^nnn 位二進制串)按順序排列,再在每個串前加一個前綴 00 構成。
  3. n+1n + 1 位格雷碼的後 2n2^n 個二進制串,可以由依此算法生成的 nn 位格雷碼(總共 2n2^nnn 位二進制串)按逆序排列,再在每個串前加一個前綴 11 構成。

綜上,n+1n + 1 位格雷碼,由 nnn 位格雷碼的 2n2^n 個二進制串按順序排列再加前綴 00,和按逆序排列再加前綴 11 構成,共 2n+12^{n+1} 個二進制串。另外,對於 nn 位格雷碼中的 2n2^n 個 二進制串,我們按上述算法得到的排列順序將它們從 02n10 \sim 2^n - 1 編號。

按該算法,22 位格雷碼可以這樣推出:

  1. 已知 1 位格雷碼爲 0011
  2. 前兩個格雷碼爲 00000101。後兩個格雷碼爲 11111010。合併得到 0000010111111010,編號依次爲 030 \sim 3

同理,33 位格雷碼可以這樣推出:

  1. 已知 22 位格雷碼爲:0000010111111010
  2. 前四個格雷碼爲:000000001001011011010010。後四個格雷碼爲:110110111111101101100100。合併得到:000000001001011011010010110110111111101101100100,編號依次爲 070 \sim 7

現在給出 nnkk,請你求出按上述算法生成的 nn 位格雷碼中的 kk 號二進制串。

分析

簡單列出 n4n \le 4 的情況就可以發現,對於第 kk 個串,去掉它的最後一位就和 n1n-1 的第 k2\lfloor\frac{k}{2}\rfloor 位上的串一樣了,且不難發現最後一位成 00111100 循環,於是直接遞歸處理。

注意題目數據要求開 unsigned long long

參考代碼

#include <cstdio>
#include <string>
#include <iostream>
#include <algorithm>
using namespace std;

typedef unsigned long long ull;

int N;
ull K;
string s;

void DFS(int t, ull k) {
	if(t == 0) return;
	DFS(t - 1, k / 2);
	if(k % 4 == 2 || k % 4 == 1) s += '1';
	else s += '0';
}

int main() {
//	freopen("code.in", "r", stdin);
//	freopen("code.out", "w", stdout);
	cin >> N >> K;
	DFS(N, K);
	cout << s << endl;
	return 0;
}

D1T2 括號樹

題目

題目描述

小 Q 是一個充滿好奇心的小朋友,有一天他在上學的路上碰見了一個大小爲 nn 的樹,樹上結點從 1n1 \sim n 編號,11 號結點爲樹的根。除 11 號結點外,每個結點有一個父親結點,uu2un2 \leq u \leq n)號結點的父親爲 fuf_u​(1fu<u1 \leq f_u < u)號結點。

小 Q 發現這個樹的每個結點上恰有一個括號,可能是( 或)。小 Q 定義 sis_i​ 爲:將根結點到 ii 號結點的簡單路徑上的括號,按結點經過順序依次排列組成的字符串。

顯然 sis_i​ 是個括號串,但不一定是合法括號串,因此現在小 Q 想對所有的 ii1in1\leq i\leq n)求出,sis_i​ 中有多少個互不相同的子串是合法括號串。

這個問題難倒了小 Q,他只好向你求助。設 sis_i​ 共有 kik_i​ 個不同子串是合法括號串, 你只需要告訴小 Q 所有 i×kii \times k_i​ 的異或和,即:

(1×k1)(2×k2)(3×k3)(n×kn) (1 \times k_1)\oplus(2 \times k_2)\oplus(3 \times k_3)\oplus \cdots\oplus (n \times k_n)

其中 \oplus 是位異或運算。

分析

就不講我考場上怎麼做的吧,要看的話直接去我的爆炸記裏面看就可以了。。。

gug_u 爲從節點 11 到節點 faufa_u 的路徑上的括號序列中,加入 uu 的括號後新增加的合法匹配的括號序列的數量。

則節點 uu 的答案爲從根到 uu 的路徑上的 gg 之和。

考慮怎麼轉移這個東西。

我們用一個棧來記錄左括號的位置。

則對於一個節點,我們分兩種情況來討論:

  • 若當前節點是左括號:將這個節點塞進棧裏,顯然沒有新增合法括號匹配,令 gu=0g_u = 0 即可;
  • 若當前節點是右括號,我們再分兩種情況討論:
    • 若當前棧是空的,則這個節點上也沒有新增合法括號匹配,令 gu=0g_u = 0 即可;
    • 否則取出棧頂 tt,不難發現節點 uu 可以和 tt 匹配增加一個合法的括號匹配,此時我們應令 gu=gt+1g_u = g_t + 1

注意在向下遞歸完後要恢復現場。 不要學我開 NN 個棧。。。

參考代碼

#include <stack>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;

typedef long long ll;
const int Maxn = 5e5;

int N;
char s[Maxn + 5];
vector<int> G[Maxn + 5];
void addedge(int u, int v) {
	G[u].push_back(v), G[v].push_back(u);
}

stack<int> stk;
ll f[Maxn + 5], g[Maxn + 5];
int fa[Maxn + 5];
void DFS(int u, int pre) {
	int tp = -1;
	if(s[u] == '(') stk.push(u);
	else {
		if(!stk.empty()) {
			tp = stk.top();
			g[u] = g[fa[tp]] + 1;
			stk.pop();
		}
	}
	f[u] = f[fa[u]] + g[u];
	for(int i = 0; i < (int)G[u].size(); i++) {
		int v = G[u][i];
		if(v == pre) continue;
		DFS(v, u);
	}
	if(tp != -1) stk.push(tp);
	else if(!stk.empty()) stk.pop();
}

int main() {
#ifdef LOACL
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	scanf("%d", &N);
	scanf("%s", s + 1);
	for(int i = 2; i <= N; i++) {
		scanf("%d", &fa[i]);
		addedge(fa[i], i);
	}
	DFS(1, 0);
	ll ans = 0;
	for(int i = 1; i <= N; i++)
		ans ^= (1LL * i * f[i]);
	printf("%lld\n", ans);
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章