bzoj5371: [Pkusc2018]星际穿越 动态规划,主席树/倍增

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] [1x1]可以从 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][j1]][j1]
同时考虑询问区间 [ 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][j1]+S[f[i][j1]][j1]+(f[i][j1]f[f[i][j1]][j1])2j1
最后的时候倍增地跳,边跳边统计答案即可。

#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)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值