1. 程式人生 > >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題。 要發現一個性質,設mn[i]mn[i]表示minx=inl[x]\min_{x=i}^nl[x],如果不能一步走到,那麼必定存在一種方案,除了第一步,每一步都是沿著mn[i]mn[i]轉移。 什麼意思呢,假設我們從xx走到yyxxyy左邊。從xxyy跳。因為一步到不了,所以肯定找到一個在xx之後的可以傳送到的最右邊的那個節點傳送過去,如果那個節點已經在y

y右邊,就肯定可以再一步傳送回yy,否則的話繼續找這樣一個傳送得最遠的節點,這個貪心是顯然正確的。

思路1

考慮每一對iimn[i]mn[i]實際上構成了一個樹形結構,特判一步到的情況。對於一個節點xx,[1x1][1\cdots x-1]可以從mn[x]mn[x]對應的節點花費1次傳送直接傳送過來,也就是他們的花費比走到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]為從ii往左走jj步最遠走到哪裡。 這個東西是可以倍增的,把jj換成2j2^jf[i][j]=f[f[i][j1]][j1]f[i][j]=f[f[i][j-1]][j-1] 同時考慮詢問區間[l,r]x[l,r]\to x可以用差分轉化成Ans(x[l,x))Ans(x[r+1,x))Ans(x\to[l,x))-Ans(x\to [r+1,x)) 再用另外一個數組S[i][j]S[i][j]表示Ans(i[f[i][j],i))Ans(i\to[f[i][j],i)) 轉移 S[i][j]=S[i][j1]+S[f[i][j1]][j1]+(f[i][j1]f[f[i][j1]][j1])2j1S[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}