UVA11350 Stern-Brocot Tree
Genius_Star · · 题解
或许更好的阅读体验。
思路:
前置知识:
a \perp b 等价于存在x, y 使得ax + by = 1 。
Stern-Brocot 树是一个包含着所有
- 对于一个
k 阶 Stern-Brocot 序列,在其任意两个相邻分数\frac{m}{n} 与\frac{m'}{n'} 之间插入它们的中位分数\frac{m + m'}{n + n'} 后形成的序列即为k + 1 阶 Stern-Brocot 序列。
例如:
容易看作二叉树的结构:
- 每个分数都是
\frac{m + m'}{n + n'} 的形式,其中\frac{m}{n} 是左上方离它最近的祖先,\frac{m'}{n'} 是右上方离它最近的祖先。
oi-wiki 上的图较为形象,大家可以看着理解下:
为什么树上的都是最简分数?为什么不会重复出现某个分数?为什么所有可能的非负的最简分数都会在树上出现?
容易发现这样一个性质,如果
证明考虑数学归纳法,初始
0 阶时有1 \cdot 1 - 0 \cdot 0 = 1 ;若当前k 阶 Stern-Brocot 序列中满足条件,那么在\frac{m}{n} 与\frac{m'}{n'} 中间插入的\frac{m + m'}{n + n'} ,相当于要证明:(m' + m)n - m(n + n') = 1 m'(n + n') - (m + m') n' = 1 第一个直接拆开
m'n + mn - mn - mn' = m'n - mn' = 1 ,第二个同理;于是得证。
同时,上面
然后来考虑插入的分数的大小关系,显然有:
即一个中位分数在它原先两个值的中间,于是树上必然没有重复的分数。
好,接下来要证所有正的最简分数
然后假设当前阶段有:
考虑
-
若
\frac{m + m'}{n + n'} = \frac{a}{b} ,与命题矛盾,退出。 -
若
\frac{m + m'}{n + n'} < \frac{a}{b} ,令m \gets m + m', n' + n 。 -
否则
\frac{m + m'}{n + n'} > \frac{a}{b} ,令m' \gets m + m', n' \gets n + n' 。
考虑证明这个过程不会无限进行下去,因为:
即:
显然
然后必然有:
前面把
然后它们的系数可以根据
而上面每次操作中
因为每个正最简分数只出现一次,所以其与树上从根到它的路径是一一对应的,即我们可以用字母
考虑这样一个问题,给出一组
容易想到从初始
-
若
L 往左走:那么左祖先不会变,右祖先会变成当前节点;即m' \gets m + m', n' \gets n + n' 。 -
若
R 往右左:同理,那么右祖先不会变,左祖先会变成当前节点;即m \gets m + m', n \gets n + n' 。
大家理解的时候可以看前面那个树的图来理解;然后我们就可以写下如下代码解决:
inline pair<int, int> getLR(string s){
int len = s.size();
int m = 0, n = 1, m_ = 1, n_ = 0;
for(int i = 0; i < len; ++i){
if(s[i] == 'L')
m_ = m + m_, n_ = n + n_;
else
m = m + m_, n = n + n_;
}
return mkp(m + m_, n + n_);
}
当长度很长时,即给定是
inline pair<int, int> getLR(vector<pair<char, int>> s){
int len = s.size();
int m = 0, n = 1, m_ = 1, n_ = 0;
for(int i = 0; i < len; ++i){
if(s[i].fi == 'L')
m_ = s[i].se * m + m_, n_ = s[i].se * n + n_;
else
m = m + s[i].se * m_, n = n + s[i].se * n_;
}
return mkp(m + m_, n + n_);
}
这种还是太程序性了,数学语言怎么表示?容易想到矩阵,即初始:
这里为啥不用像分数那样上面分子下面分母呢?主要是此时初始根节点的状态
然后考虑:
那么可以推出
即:
于是求
于是求
那么现在考虑给定一个分数
那么可以写下如下代码:
inline string backLR(int m, int n){
string ans = "";
Mat S;
while(1){
auto t = f(S);
if(t == mkp(m, n))
break;
if(mkp(m, n) < t){
S = S * L;
ans.push_back('L');
}
else{
S = S * R;
ans.push_back('R');
}
}
return ans;
}
显然,这个效率较为低下,且要进行矩阵运算;考虑怎么优化一下,注意到:
那么:
设
那么可以写出如下代码:
inline string backLR(int m, int n){
string ans = "";
while(m != n){
if(m < n){
ans.push_back('L');
n = n - m;
}
else{
ans.push_back('R');
m = m - n;
}
}
return ans;
}
你发现这特别像更像减损法,于是可以用辗转相除法类似的思路去优化,即:
inline vector<pair<char, int>> backLR(int m, int n){
vector<pair<char, int>> ans;
while(m && n && m != n){
if(m < n){
if(n % m == 0)
ans.push_back({'L', n / m - 1});
else
ans.push_back({'L', n / m});
n = n % m;
}
else{
if(m % n == 0)
ans.push_back({'R', m / n - 1});
else
ans.push_back({'R', m / n});
m = m % n;
}
}
return ans;
}
此时就可以做到
然后对于一个分数
对于树上问题,容易想到 LCA,那么考虑 Stern-Brocot 树上的两个点
同理,
显然单次时间复杂度都是
link
完整代码:
#include<bits/stdc++.h>
#define int long long
#define ls(k) k << 1
#define rs(k) k << 1 | 1
#define lowbit(x) x & (-x)
#define fi first
#define se second
#define popcnt(x) __builtin_popcount(x)
#define open(s1, s2) freopen(s1, "r", stdin), freopen(s2, "w", stdout);
#define mkp(x, y) make_pair(x, y)
using namespace std;
typedef __int128 __;
typedef long double lb;
typedef double db;
typedef unsigned long long ull;
typedef long long ll;
bool Begin;
inline ll read(){
ll x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-')
f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
inline void write(ll x){
if(x < 0){
putchar('-');
x = -x;
}
if(x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
inline pair<int, int> getLR(vector<pair<char, int>> s){
int len = s.size();
int m = 0, n = 1, m_ = 1, n_ = 0;
for(int i = 0; i < len; ++i){
if(s[i].fi == 'L')
m_ = s[i].se * m + m_, n_ = s[i].se * n + n_;
else
m = m + s[i].se * m_, n = n + s[i].se * n_;
}
return mkp(m + m_, n + n_);
}
inline vector<pair<char, int>> backLR(int m, int n){
vector<pair<char, int>> ans;
while(m && n && m != n){
if(m < n){
if(n % m == 0)
ans.push_back({'L', n / m - 1});
else
ans.push_back({'L', n / m});
n = n % m;
}
else{
if(m % n == 0)
ans.push_back({'R', m / n - 1});
else
ans.push_back({'R', m / n});
m = m % n;
}
}
return ans;
}
inline pair<int, int> getkfa(int m, int n, int k){
auto V = backLR(m, n);
int sum = 0, len = V.size();
for(int i = 0; i < len; ++i)
sum += V[i].se;
if(sum < k)
return mkp(-1, -1);
vector<pair<char, int>> fa;
for(int i = 0; i < len; ++i){
if(!k)
break;
if(V[i].se <= k){
fa.push_back(V[i]);
k -= V[i].se;
}
else{
fa.push_back(mkp(V[i].fi, k));
k = 0;
}
}
return getLR(fa);
}
inline pair<pair<int, int>, pair<int, int>> range(int p, int q){
auto s = backLR(p, q);
int len = s.size();
int m = 0, n = 1, m_ = 1, n_ = 0;
for(int i = 0; i < len; ++i){
if(s[i].fi == 'L')
m_ = s[i].se * m + m_, n_ = s[i].se * n + n_;
else
m = m + s[i].se * m_, n = n + s[i].se * n_;
}
return mkp(mkp(m, n), mkp(m_, n_));
}
inline pair<int, int> getlca(int a, int b, int c, int d){
auto A = backLR(a, b), B = backLR(c, d);
int s1 = 0, s2 = 0;
for(auto v : A)
s1 += v.se;
for(auto v : B)
s2 += v.se;
if(s1 < s2){
swap(a, c), swap(b, d);
swap(A, B);
}
vector<pair<char, int>> lca;
int j = 0;
for(int i = 0; i < (int)A.size(); ++i){
int s = A[i].se;
while(j < (int)B.size() && s){
if(B[j].fi != A[i].fi)
break;
if(B[j].se <= s){
s -= B[j].se;
++j;
}
else{
B[j].se -= s;
s = 0;
}
}
if(j == (int)B.size() || s){
lca.push_back(mkp(A[i].fi, A[i].se - s));
break;
}
lca.push_back(A[i]);
}
return getLR(lca);
}
int T;
string s;
signed main(){
T = read();
while(T--){
vector<pair<char, int>> V;
cin >> s;
for(auto c : s)
V.push_back(mkp(c, 1));
auto t = getLR(V);
write(t.fi);
putchar('/');
write(t.se);
putchar('\n');
}
return 0;
}