【NOIP模拟】board(线段树维护二进制,树序号化为二进制)
SOURCE:NOIP2016-RZZ-2 T3
题目描述
给出这样一棵“二叉树”:
-
- 每个节点有左右两个儿子,并如下定义每个节点的高度:假设父亲节点的高度为 h ,那么他的两个儿子的节点的高度都是 h + 1 ,相同高度的所有节点称作一层。
- 每个节点的左儿子的子树都在右儿子的子树的左边,每一层相邻的两个节点之间有一条边。
下面是一个例子:
每一条图上的路径用一个字符串表示,字符串中的每一个字符表示一个移动。字符仅包含如下五种:
-
- 1:表示移动到当前节点的左儿子
- 2:表示移动到当前节点的右儿子
- U:表示移动到当前节点的父亲节点
- L:表示移动到当前节点同层的左边的节点(保证当前节点在这一层中不是最左边的节点)
- R:表示移动到当前节点同层的右边的节点(保证当前节点在这一层中不是最右边的节点)
用一条路径来表示这条路径的终点,例如路径:221LU 就表示上图中的节点 A 。
给出两条路径,你的任务是求出着两条路径的终点之间的最短路。
输入格式
输入两行,每行一个字符串,分别表示两条路径。
输出格式
输出一行一个整数,表示能得到的串的总数。
样例数据 1
输入
221LU
12L2
输出
3
备注
【数据规模与约定】
用 D 表示所有经过的节点中,深度最大的节点的深度;S 表示输入字符串的最大长度。对于 10% 的数据,D≤10。对于 30% 的数据,D≤50。
对于 50% 的数据,D≤1000。
对于 70% 的数据,D≤20000。
对于 100% 的数据,D≤100000;S≤100000。
【题目分析】
难题一:找到两个点的终点。
因为路径可能很长,十进制数很容易炸,所以考虑将树的编号用二进制表示:
向左儿子走二进制数后面加$0$, 右儿子加$1$, 向父亲走删除二进制数最后的数字。这些都好实现, 不过在向左时我们需要给二进制数加$1$(右减$1$),此时便涉及到进位。
【线段树维护二进制数】
将二进制数建成一颗线段树,这样在加$1$时,我们只需找到第一个$0$将它修改为$1$, 并把此前的$1$全部修改为$0$即可(减$1$反之)
查找第一个$x(0 / 1)$时,我们先在右儿子中查找(保证最前),若未找到则再在左儿子中找。
将最后的二进制数导出,我们就知道此时点到根的路径了(不走横向边)。
难题2:怎样才是最短路?
画图观察即可知,最短路径要求两点至少得在同一深度,则先将深度较大的点上移至同一深度,$ans += |dep_{i} - dep_{j}|$
这时再加上他们中间的距离即可。但是如果再同时往上跳,他们间的距离可能更小T_T,这时就需要从根节点开始,分别沿着从难题一中导出的路径向下找出剩余距离的最短值。
可以发现,若当前层距离为$dis$,则下一层的距离会根据两点走的方向发生变化:
$or$ $dis = dis × 2$
$dis = dis × 2 - 1$
$dis = dis × 2 + 1$
最后$ans += min\{dis + 2 × (dep - i)\} (其中dep为终点深度较小的深度, i为跳至的深度)$
具体可以看代码。
【code】
#include<iostream> #include<cstdio> #include<cstring> #include<string> #include<cmath> #include<algorithm> #include<vector> using namespace std; const int D = 100005, S = 100005; char s[S], t[S]; int ls, lt, ans, ret; struct node{ int len, sum, tag; node():len(0), sum(0), tag(0){} }; namespace SegTree{ node tr[D * 4]; inline void upt(int k){ int lc = k << 1, rc = k << 1 | 1; tr[k].sum = tr[lc].sum + tr[rc].sum; } inline void cover(int k, int v){ tr[k].sum = (v - 1) * tr[k].len; tr[k].tag = v; } inline void pushDown(int k){ int lc = k << 1, rc = k << 1 | 1; if(tr[k].tag){ cover(lc, tr[k].tag); cover(rc, tr[k].tag); tr[k].sum = (tr[k].tag - 1) * tr[k].len; tr[k].tag = 0; } } inline void build(int k, int l, int r){ tr[k].len = r - l + 1; if(l == r) return; int mid = l + r >> 1, lc = k << 1, rc = k << 1 | 1; build(lc, l, mid); build(rc, mid + 1, r); upt(k); } inline void modify(int k, int l, int r, int x, int y, int v){ if(x <= l && r <= y){ cover(k, v); return; } pushDown(k); int mid = l + r >> 1, lc = k << 1, rc = k << 1 | 1; if(x <= mid) modify(lc, l, mid, x, y, v); if(y > mid) modify(rc, mid + 1, r, x, y, v); upt(k); } inline int query_1(int k, int l, int r, int pos){ if(pos < l) return 0; if(tr[k].sum == 0) return 0; if(l == r) return tr[k].sum == 1 ? l : 0; int mid = l + r >> 1, lc = k << 1, rc = k << 1 | 1; pushDown(k); int ret = 0; ret = query_1(rc, mid + 1, r, pos); if(!ret) ret = query_1(lc, l, mid, pos); return ret; } inline int query_0(int k, int l, int r,int pos){ if(pos < l) return 0; if(tr[k].sum == tr[k].len) return 0; if(l == r) return tr[k].sum == 0 ? l : 0; pushDown(k); int ret = 0; int mid = l + r >> 1, lc = k << 1, rc = k << 1 | 1; ret = query_0(rc, mid + 1, r, pos); if(!ret) ret = query_0(lc, l, mid, pos); return ret; } inline void treeExport(int k, int l, int r, char *p){ if(l == r){ p[l] = tr[k].sum + '0'; // if(l == 1) cout<<"!!!!!!!"<<p[l]<<endl; return; } pushDown(k); int mid = l + r >> 1, lc = k << 1, rc = k << 1 | 1; treeExport(lc, l, mid, p); treeExport(rc, mid + 1, r, p); } }using namespace SegTree; inline void loadIn(char *f, int &q){ static char p[S]; scanf("%s", p + 1); int maxx = strlen(p + 1); q = 0; memset(tr, 0, sizeof tr); build(1, 1, maxx); for(int i = 1; i <= maxx; i++){ switch(p[i]){ case '1':{ q++; modify(1, 1, maxx, q, q, 1); } break; case '2':{ q++; modify(1, 1, maxx, q, q, 2); } break; case 'U':{ q--; } break; case 'R':{ int first_0 = query_0(1, 1, maxx, q); modify(1, 1, maxx, first_0, first_0, 2); modify(1, 1, maxx, first_0 + 1, q, 1); } break; case 'L':{ int first_1 = query_1(1, 1, maxx, q); modify(1, 1, maxx, first_1, first_1, 1); modify(1, 1, maxx, first_1 + 1, q, 2); } break; default:break; } } treeExport(1, 1, maxx, f); } inline int Re(){ int i = 0, f = 1; char ch = getchar(); for(; (ch < '0' || ch > '9') && ch != '-'; ch = getchar()); if(ch == '-') ch = getchar(), f = -1; for(; ch >= '0' && ch <= '9'; ch = getchar()) i = (i << 3) + (i << 1) + (ch - '0'); return i * f; } inline void Wr(int x){ if(x < 0) putchar('-'), x = -x; if(x > 9) Wr(x / 10); putchar(x % 10 + '0'); } int main(){ loadIn(s, ls), loadIn(t, lt); ans = abs(ls - lt); int n = min(ls, lt), dis = 0; ret = 2 * n; for(int i = 1; i <= n; i++){ if(s[i] == t[i]) dis = dis * 2; else if(s[i] == '0' && t[i] == '1') dis = 2 * dis + 1; else if(s[i] == '1' && t[i] == '0'){ dis = 2 * dis - 1; if(dis < 0) dis = -dis, swap(s, t); } if(dis > ret) break; ret = min(ret, dis + 2 * (n - i)); } // cout<<s+1<<" "<<t + 1<<endl; // Wr(ans), putchar(' '); Wr(ans + ret), putchar('\n'); return 0; }