当前位置: 代码迷 >> 综合 >> 【CSP-S2019】D1T1 格雷码 D1T2 括号树 题解
  详细解决方案

【CSP-S2019】D1T1 格雷码 D1T2 括号树 题解

热度:13   发布时间:2023-11-21 06:37:50.0

CSP-S2019 D1T1 格雷码

题目

题目描述

通常,人们习惯将所有 nnn 位二进制串按照字典序排列,例如所有 222 位二进制串按字典序从小到大排列为:000000010101111111101010

格雷码(Gray Code)是一种特殊的 nnn 位二进制串排列法,它要求相邻的两个二进制串间恰好有一位不同,特别地,第一个串与最后一个串也算作相邻。

所有 2 位二进制串按格雷码排列的一个例子为:000000010101111111101010

nnn 位格雷码不止一种,下面给出其中一种格雷码的生成算法:

  1. 1 位格雷码由两个 111 位二进制串组成,顺序为:000111
  2. n+1n + 1n+1 位格雷码的前 2n2^n2n 个二进制串,可以由依此算法生成的 nnn 位格雷码(总共 2n2^n2nnnn 位二进制串)按顺序排列,再在每个串前加一个前缀 000 构成。
  3. n+1n + 1n+1 位格雷码的后 2n2^n2n 个二进制串,可以由依此算法生成的 nnn 位格雷码(总共 2n2^n2nnnn 位二进制串)按逆序排列,再在每个串前加一个前缀 111 构成。

综上,n+1n + 1n+1 位格雷码,由 nnn 位格雷码的 2n2^n2n 个二进制串按顺序排列再加前缀 000,和按逆序排列再加前缀 111 构成,共 2n+12^{n+1}2n+1 个二进制串。另外,对于 nnn 位格雷码中的 2n2^n2n 个 二进制串,我们按上述算法得到的排列顺序将它们从 0?2n?10 \sim 2^n - 10?2n?1 编号。

按该算法,222 位格雷码可以这样推出:

  1. 已知 1 位格雷码为 000111
  2. 前两个格雷码为 000000010101。后两个格雷码为 111111101010。合并得到 000000010101111111101010,编号依次为 0?30 \sim 30?3

同理,333 位格雷码可以这样推出:

  1. 已知 222 位格雷码为:000000010101111111101010
  2. 前四个格雷码为:000000000001001001011011011010010010。后四个格雷码为:110110110111111111101101101100100100。合并得到:000000000001001001011011011010010010110110110111111111101101101100100100,编号依次为 0?70 \sim 70?7

现在给出 nnnkkk,请你求出按上述算法生成的 nnn 位格雷码中的 kkk 号二进制串。

分析

简单列出 n≤4n \le 4n4 的情况就可以发现,对于第 kkk 个串,去掉它的最后一位就和 n?1n-1n?1 的第 ?k2?\lfloor\frac{k}{2}\rfloor?2k?? 位上的串一样了,且不难发现最后一位成 000111111000 循环,于是直接递归处理。

注意题目数据要求开 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 是一个充满好奇心的小朋友,有一天他在上学的路上碰见了一个大小为 nnn 的树,树上结点从 1?n1 \sim n1?n 编号,111 号结点为树的根。除 111 号结点外,每个结点有一个父亲结点,uuu2≤u≤n2 \leq u \leq n2un)号结点的父亲为 fuf_ufu??(1≤fu<u1 \leq f_u < u1fu?<u)号结点。

小 Q 发现这个树的每个结点上恰有一个括号,可能是( 或)。小 Q 定义 sis_isi?? 为:将根结点到 iii 号结点的简单路径上的括号,按结点经过顺序依次排列组成的字符串。

显然 sis_isi?? 是个括号串,但不一定是合法括号串,因此现在小 Q 想对所有的 iii1≤i≤n1\leq i\leq n1in)求出,sis_isi?? 中有多少个互不相同的子串是合法括号串。

这个问题难倒了小 Q,他只好向你求助。设 sis_isi?? 共有 kik_iki?? 个不同子串是合法括号串, 你只需要告诉小 Q 所有 i×kii \times k_ii×ki?? 的异或和,即:

(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) (1×k1?)?(2×k2?)?(3×k3?)???(n×kn?)

其中 ?\oplus? 是位异或运算。

分析

就不讲我考场上怎么做的吧,要看的话直接去我的爆炸记里面看就可以了。。。

gug_ugu? 为从节点 111 到节点 faufa_ufau? 的路径上的括号序列中,加入 uuu 的括号后新增加的合法匹配的括号序列的数量。

则节点 uuu 的答案为从根到 uuu 的路径上的 ggg 之和。

考虑怎么转移这个东西。

我们用一个栈来记录左括号的位置。

则对于一个节点,我们分两种情况来讨论:

  • 若当前节点是左括号:将这个节点塞进栈里,显然没有新增合法括号匹配,令 gu=0g_u = 0gu?=0 即可;
  • 若当前节点是右括号,我们再分两种情况讨论:
    • 若当前栈是空的,则这个节点上也没有新增合法括号匹配,令 gu=0g_u = 0gu?=0 即可;
    • 否则取出栈顶 ttt,不难发现节点 uuu 可以和 ttt 匹配增加一个合法的括号匹配,此时我们应令 gu=gt+1g_u = g_t + 1gu?=gt?+1

注意在向下递归完后要恢复现场。 不要学我开 NNN 个栈。。。

参考代码

#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 LOACLfreopen("in.txt", "r", stdin);freopen("out.txt", "w", stdout);
#endifscanf("%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;
}