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題。 要發現一個性質,設表示,如果不能一步走到,那麼必定存在一種方案,除了第一步,每一步都是沿著轉移。 什麼意思呢,假設我們從走到,在左邊。從往跳。因為一步到不了,所以肯定找到一個在之後的可以傳送到的最右邊的那個節點傳送過去,如果那個節點已經在右邊,就肯定可以再一步傳送回,否則的話繼續找這樣一個傳送得最遠的節點,這個貪心是顯然正確的。
思路1
考慮每一對和實際上構成了一個樹形結構,特判一步到的情況。對於一個節點,可以從對應的節點花費1次傳送直接傳送過來,也就是他們的花費比走到中所有節點的花費增加了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
記錄為從往左走步最遠走到哪裡。 這個東西是可以倍增的,把換成。 同時考慮詢問區間可以用差分轉化成 再用另外一個數組表示 轉移