程式設計之美初賽第一場 樹

2021-06-21 14:51:05 字數 4396 閱讀 9953

時間限制:

4000ms

單點時限:

2000ms

記憶體限制:

256mb

有乙個n個節點的樹,其中點1是根。初始點權值都是0。

乙個節點的深度定義為其父節點的深度+1,。特別的,根節點的深度定義為1。

現在需要支援一系列以下操作:給節點u的子樹中,深度在l和r之間的節點的權值(這裡的深度依然從整個樹的根節點開始計算),都加上乙個數delta。

問完成所有操作後,各節點的權值是多少。

為了減少巨大輸出帶來的開銷,假設完成所有操作後,各節點的權值是answer[1..n],請你按照如下方式計算出乙個hash值(請選擇合適的資料型別,注意避免溢位的情況)。最終只需要輸出這個hash值即可。

mod =1000000007; // 10^9 + 7

magic= 12347;

hash =0;

for i= 1 to n do

hash = (hash * magic + answer[i]) mod mod;

endfor

第一行乙個整數t (1 ≤ t ≤ 5),表示資料組數。

接下來是t組輸入資料,測試資料之間沒有空行。

每組資料格式如下:

第一行乙個整數n (1 ≤ n ≤ 105),表示樹的節點總數。

接下來n - 1行,每行1個數,a (1 ≤ a ≤ n),依次表示2..n節點的父親節點的編號。

接下來乙個整數q(1 ≤ q ≤ 105),表示操作總數。

接下來q行,每行4個整數,u, l, r, delta (1 ≤ u ≤ n, 1 ≤ l ≤ r ≤ n, -109 ≤ delta ≤ 109),代表一次操作。

對每組資料,先輸出一行「case x: 」,x表示是第幾組資料,然後接這組資料答案的hash值。

小資料:1 ≤ n, q ≤ 1000

大資料:1 ≤ n, q ≤ 105

點1的子樹中有1,2,3三個節點。其中深度在2-3之間的是點2和點3。

點2的子樹中有2,3兩個節點。其中沒有深度為1的節點。

所以,執行完所有操作之後,只有2,3兩點的權值增加了1。即答案是0 1 1。再計算對應的hash值即可。

樣例輸入

131

221 2 3 1

2 1 1 1

樣例輸出

case 1: 12348
注意表示為father的時候每個結點的高度並不是可以直接得到的,需要深搜,先貼一下我的錯誤**:

#include #include #include #include #include using namespace std;

int t, i, j, t, n, q, u, l, r, delta, d[100002], answer[100002],father;

vectortree[100002];

void dfs(int u, int l, int r, int delta)

}int main()

scanf("%d", &q);

for (i = 0; i < q; ++i)

for (i = 1; i <= n; ++i)

hash = (hash * magic + answer[i]) % mod;

printf("case %d: %lld\n",t,hash);

for (i = 1; i <= n; ++i)

tree[i].clear();

} return 0;

}

據說如果把上面的d[i] = d[father] + 1改掉我上面暴力的方法都可以通過,但是比較好的方法是用樹狀陣列把查詢都先記錄下來,然後再深搜一次更新,補充一下樹狀陣列的概念:

來觀察這個圖:

樹狀陣列的結構圖

令這棵樹的結點編號為c1,c2...cn。令每個結點的值為這棵樹的值的總和,那麼容易發現:

c1 = a1

c2 = a1 + a2

c3 = a3

c4 = a1 + a2 + a3 + a4

c5 = a5

c6 = a5 + a6

c7 = a7

c8 = a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8

...c16 = a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + a11 + a12 + a13 + a14 + a15 + a16

這裡有乙個有趣的性質:

設節點編號為x,那麼這個節點管轄的區間為2^k(其中k為x二進位制末尾0的個數)個元素。因為這個區間最後乙個元素必然為ax,

所以很明顯:cn = a(n – 2^k + 1) + ... + an

算這個2^k有乙個快捷的辦法,定義乙個函式如下即可:

1

2

3

intlowbit(intx)

利用機器補碼特性,也可以寫成:

1

2

3

intlowbit(intx)

當想要查詢乙個sum(n)(求a[n]的和),可以依據如下演算法即可:

step1: 令sum = 0,轉第二步;

step2: 假如n <= 0,演算法結束,返回sum值,否則sum = sum + cn,轉第三步;

step3: 令n = n – lowbit(n),轉第二步。

可以看出,這個演算法就是將這乙個個區間的和全部加起來,為什麼是效率是log(n)的呢?以下給出證明:

n = n – lowbit(n)這一步實際上等價於將n的二進位制的最後乙個1減去。而n的二進位制裡最多有log(n)個1,所以查詢效率是log(n)的。

那麼修改呢,修改乙個節點,必須修改其所有祖先,最壞情況下為修改第乙個元素,最多有log(n)的祖先。

所以修改演算法如下(給某個結點i加上x):

step1: 當i > n時,演算法結束,否則轉第二步;

step2: ci = ci + x, i = i + lowbit(i)轉第一步。

i = i +lowbit(i)這個過程實際上也只是乙個把末尾1補為0的過程。

對於 陣列求和來說樹狀陣列簡直太快了!

注:

求lowbit(x)的建議公式:

lowbit(x):=x and (x xor (x - 1));

或lowbit(x):=x and (-x);

lowbit(x)即為2^k的值。

最後轉乙個朋友的**:

#include #include #include using namespace std;

const int maxn = 100000 + 100;

const int mod = 1000000007;

int qlast[maxn], last[maxn];

struct qq q[maxn];

struct ee e[maxn];

long long ans[maxn];

long long hash1;

long long t[maxn];

int n;

void add(int d, int del)

}long long getsum(int d)

return ret;

}void solve(int x, int d)

ans[x] += getsum(d);

while (last[x] != 0)

i = qlast[x];

while (i != 0) }

int main()

scanf("%d", &n);

for (int i = 2; i <= n; ++i)

int q;

scanf("%d", &q);

for (int i = 1; i <= q; ++i)

solve(1, 1);

printf("case %d: ", tt);

int magic = 12347;

hash1 = 0;

for (int i = 1; i <= n; ++i)

cout << hash1 << endl;

} return 0;

}

程式設計之美初賽第一場 樹

時間限制 4000ms 單點時限 2000ms 記憶體限制 256mb 有乙個n個節點的樹,當中點1是根。初始點權值都是0。乙個節點的深度定義為其父節點的深度 1,特別的,根節點的深度定義為1。問完畢全部操作後,各節點的權值是多少。為了降低巨大輸出帶來的開銷,如果完畢全部操作後,各節點的權值是ans...

程式設計之美初賽第一場 樹

時間限制 4000ms 單點時限 2000ms 記憶體限制 256mb 有乙個n個節點的樹。當中點1是根。初始點權值都是0。乙個節點的深度定義為其父節點的深度 1,特別的。根節點的深度定義為1。問完畢全部操作後,各節點的權值是多少。為了降低巨大輸出帶來的開銷。如果完畢全部操作後。各節點的權值是ans...

程式設計之美初賽第一場

時間限制 12000ms 單點時限 6000ms 記憶體限制 256mb a市是乙個高度規劃的城市。可是科技高階發達的地方,居民們也不能忘記運動和鍛鍊,因此城市規劃局在設計a市的時候也要考慮為居民們建造乙個活動中心。方便居住在a市的居民們能隨時開展運動。鍛鍊強健的身心。城市規劃局希望活動中心的位置滿...