bzoj5371: [Pkusc2018]星际穿越
Description
有n个星球,它们的编号是1到n,它们坐落在同一个星系内,这个星系可以抽象为一条数轴,每个星球都是数轴上的一个点,
特别地,编号为i的星球的坐标是i。
一开始,由于科技上的原因,这n个星球的居民之间无法进行交流,因此他们也不知道彼此的存在。
现在,这些星球独立发展出了星际穿越与星际交流的工具。
对于第i个星球,他通过发射强力信号,成功地与编号在[Li,i-1]的所有星球取得了联系(编号为1的星球没有发出任何信号),
取得联系的两个星球会建立双向的传送门,对于建立了传送门的两个星球u,v,u上的居民可以花费1单位时间传送到v,
v上的居民也可以花费1单位时间传送到u,我们用dist(x,y)表示从编号为x的星球出发,通过一系列星球间的传送门,
传送到编号为y的星球最少需要花费的时间。
现在有q个星际商人,第i个商人初始所在的位置是xi,他的目的地是[Li,Ri]中的其中一个星球,保证Li<Ri<xi。
他会在这些星球中等概率挑选一个星球y(每个星球都有一样的概率被选中作为目的地),
然后通过一系列星球的传送门,花费最少的时间到达星球y。
商人想知道他花费的期望时间是多少?也就是计算∑dist(xi,y)/(Ri-Li+1),其中y<=Li<=Ri
Input
第一行一个正整数n,表示星球的个数。
第二行n-1个正整数,第i个正整数为Li+1,
表示编号在[Li+1,i]区间内所有星球已经与编号为i+1的星球取得了联系,并且可以通过花费1单位进行彼此的传输。保证Li+1≤i
第三行一个正整数q,表示询问组数。
接下来q行,每行三个数字Li,Ri,xi,表示在[Li,Ri]这个区间中等概率选择一个星球y,dist(xi,y)的期望。
保证Li<Ri<xi,n,q≤3×10^5
Output
对于每组询问,注意到答案必然是一个有理数,因此以p/q的格式输出这个有理数,要求gcd(p,q)=1
如果答案为整数m,输出m/1
Sample Input
7
1 1 2 1 4 6
5
3 4 6
1 5 7
1 2 4
1 2 6
1 3 5
Sample Output
3/2
13/5
3/2
2/1
1/1
分析
一道很强的Dp题。
要发现一个性质,设
m
n
[
i
]
mn[i]
mn[i]表示
min
x
=
i
n
l
[
x
]
\min_{x=i}^nl[x]
minx=inl[x],如果不能一步走到,那么必定存在一种方案,除了第一步,每一步都是沿着
m
n
[
i
]
mn[i]
mn[i]转移。
什么意思呢,假设我们从
x
x
x走到
y
y
y,
x
x
x在
y
y
y左边。从
x
x
x往
y
y
y跳。因为一步到不了,所以肯定找到一个在
x
x
x之后的可以传送到的最右边的那个节点传送过去,如果那个节点已经在
y
y
y右边,就肯定可以再一步传送回
y
y
y,否则的话继续找这样一个传送得最远的节点,这个贪心是显然正确的。
思路1
考虑每一对
i
i
i和
m
n
[
i
]
mn[i]
mn[i]实际上构成了一个树形结构,特判一步到的情况。对于一个节点
x
x
x,
[
1
⋯
x
−
1
]
[1\cdots x-1]
[1⋯x−1]可以从
m
n
[
x
]
mn[x]
mn[x]对应的节点花费1次传送直接传送过来,也就是他们的花费比走到
m
n
[
x
]
mn[x]
mn[x]中所有节点的花费增加了1。
这个过程可以采用主席树维护。
#include<bits/stdc++.h>
const int T = 8e6 + 10, N = 3e5 + 10;
int ri() {
char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
int s[T], tg[T], ls[T], rs[T], l[N], mn[N], rt[N], sz, n;
void Ins(int &np, int lp, int L, int R, int st, int ed) {
s[np = ++sz] = s[lp] + ed - st + 1; tg[np] = tg[lp];
ls[np] = ls[lp]; rs[np] = rs[lp];
if(L == st && ed == R) return void(++tg[np]); int m = L + R >> 1;
if(st <= m) Ins(ls[np], ls[lp], L, m, st, std::min(ed, m));
if(ed > m) Ins(rs[np], rs[lp], m + 1, R, std::max(m + 1, st), ed);
}
int Que(int p, int L, int R, int st, int ed) {
if(L == st && ed == R || !p) return s[p];
int m = L + R >> 1, r = tg[p] * (ed - st + 1);
if(st <= m) r += Que(ls[p], L, m, st, std::min(ed, m));
if(ed > m) r += Que(rs[p], m + 1, R, std::max(m + 1, st), ed);
return r;
}
int main() {
n = ri();
for(int i = 2;i <= n; ++i) l[i] = ri();
mn[n] = l[n];
for(int i = n - 1; i; --i) mn[i] = std::min(mn[i + 1], l[i]);
for(int i = 2;i <= n; ++i)
Ins(rt[i], rt[mn[i]], 1, n, 1, i - 1);
for(int m = ri();m--;) {
int L = ri(), R = ri(), x = ri(), p = R - L + 1, q = p;
if(L < l[x]) p += Que(rt[l[x]], 1, n, L, std::min(R, l[x] - 1));
int d = std::__gcd(p, q);
printf("%d/%d\n", p / d, q / d);
}
return 0;
}
然而这个方法bzoj上T了。
考虑优化之,可以采用离线+树状数组的方法。
#include<bits/stdc++.h>
const int N = 3e5 + 10;
int ri() {
char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
int pr[N], nx[N], Pr[N], Nx[N], l[N], p[N], q[N], tp, mn[N], rt[N], sz, n;
void add(int u, int i) {nx[i] = pr[u]; pr[u] = i;}
struct Q {int l, r, id;}To[N];
void AQ(int u, int l, int r, int id) {
To[++tp].l = l; To[tp].r = r; To[tp].id = id;
Nx[tp] = Pr[u]; Pr[u] = tp;
}
struct B {
int t1[N], t2[N];
void A(int i, int v) {for(int x = i;x <= n;x += x&-x) t1[x] += v, t2[x] += 1LL * v * i;}
int Q(int i) {
int r1 = 0, r2 = 0;
for(int x = i;x; x -= x&-x) r1 += t1[x], r2 += t2[x];
return r1 * (i + 1) - r2;
}
void A(int l, int r, int v) {A(l, v); A(r + 1, -v);}
int Q(int l, int r) {return Q(r) - Q(l - 1);}
}T;
void Dfs(int u) {
if(u != 1) T.A(1, u - 1, 1);
for(int i = Pr[u]; i; i = Nx[i]) p[To[i].id] += T.Q(To[i].l, To[i].r);
for(int i = pr[u]; i; i = nx[i]) Dfs(i);
if(u != 1) T.A(1, u - 1, -1);
}
int main() {
n = ri();
for(int i = 2;i <= n; ++i) l[i] = ri();
mn[n] = l[n]; add(mn[n], n);
for(int i = n - 1; i > 1; --i) mn[i] = std::min(mn[i + 1], l[i]), add(mn[i], i);
int m = ri();
for(int i = 1;i <= m; ++i) {
int L = ri(), R = ri(), x = ri(); p[i] = q[i] = R - L + 1;
if(L < l[x]) AQ(l[x], L, std::min(R, l[x] - 1), i);
}
Dfs(1);
for(int i = 1;i <= m; ++i) {
int d = std::__gcd(p[i], q[i]);
printf("%d/%d\n", p[i] / d, q[i] / d);
}
return 0;
}
愉快地卡了过去
思路2
记录
f
[
i
]
[
j
]
f[i][j]
f[i][j]为从
i
i
i往左走
j
j
j步最远走到哪里。
这个东西是可以倍增的,把
j
j
j换成
2
j
2^j
2j。
f
[
i
]
[
j
]
=
f
[
f
[
i
]
[
j
−
1
]
]
[
j
−
1
]
f[i][j]=f[f[i][j-1]][j-1]
f[i][j]=f[f[i][j−1]][j−1]
同时考虑询问区间
[
l
,
r
]
→
x
[l,r]\to x
[l,r]→x可以用差分转化成
A
n
s
(
x
→
[
l
,
x
)
)
−
A
n
s
(
x
→
[
r
+
1
,
x
)
)
Ans(x\to[l,x))-Ans(x\to [r+1,x))
Ans(x→[l,x))−Ans(x→[r+1,x))
再用另外一个数组
S
[
i
]
[
j
]
S[i][j]
S[i][j]表示
A
n
s
(
i
→
[
f
[
i
]
[
j
]
,
i
)
)
Ans(i\to[f[i][j],i))
Ans(i→[f[i][j],i))
转移
S
[
i
]
[
j
]
=
S
[
i
]
[
j
−
1
]
+
S
[
f
[
i
]
[
j
−
1
]
]
[
j
−
1
]
+
(
f
[
i
]
[
j
−
1
]
−
f
[
f
[
i
]
[
j
−
1
]
]
[
j
−
1
]
)
⋅
2
j
−
1
S[i][j]=S[i][j-1]+S[f[i][j-1]][j-1]+(f[i][j-1]-f[f[i][j-1]][j-1])\cdot 2 ^{j-1}
S[i][j]=S[i][j−1]+S[f[i][j−1]][j−1]+(f[i][j−1]−f[f[i][j−1]][j−1])⋅2j−1
最后的时候倍增地跳,边跳边统计答案即可。
#include<bits/stdc++.h>
const int N = 3e5 + 10, inf = 0x3f3f3f3f;
int l[N], f[N][21], s[N][21], lg[N], n;
int ri() {
char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
int Cal(int st, int p) {
if(l[p] <= st) return p - st;
int r = p - l[p], c = 1; p = l[p];
for(int i = lg[p]; ~i; --i)
if(f[p][i] > st) r += s[p][i] + (p - f[p][i]) * c, p = f[p][i], c += 1 << i;
return r + (p - st) * (c + 1);
}
int main() {
n = ri(); l[1] = 1; for(int i = 2;i <= n; ++i) l[i] = ri(), lg[i] = lg[i >> 1] + 1;
f[n + 1][0] = inf;
for(int i = n; i; --i)
f[i][0] = std::min(f[i + 1][0], l[i]), s[i][0] = i - f[i][0];
for(int j = 1;j <= lg[n]; ++j)
for(int i = 1;i <= n; ++i) if(f[i][j - 1]) {
f[i][j] = f[f[i][j - 1]][j - 1];
s[i][j] = s[i][j - 1] + s[f[i][j - 1]][j - 1] + (f[i][j - 1] - f[i][j] << j - 1);
}
for(int m = ri();m--;) {
int l = ri(), r = ri(), x = ri();
int p = Cal(l, x) - Cal(r + 1, x), q = r - l + 1, d = std::__gcd(p, q);
printf("%d/%d\n", p/d, q/d);
}
return 0;
}
实测树状数组最快,复杂度都是 O ( n l o g n ) O(nlogn) O(nlogn)