【總結】JOISC2022
Day1
T1:給定一棵樹,有 \(m\) 個人,每個人從 \(s_i\) 出發要到 \(t_i\),每次可以指定一個人走一條邊。問是否存在一種方案讓每個人都到 \(t_i\),且滿足任何兩個人不同時出現在同一個節點,且每個人不走重復路徑
分析一下,對於一個人的操作一定是連續的。如果我們先移動 \(x\),再移動 \(y\),然后移動 \(x\),說明 \(y\) 擋住了 \(x\),而 \(x\) 一定不會擋住 \(y\)。那么我們先移動 \(y\),再移動 \(x\) 一定更優。
所以我們要求一個順序 \(p\),使得按順序依次將每個人移動到終點。那么對於兩個人 \(i,j\),如果 \(s_i\) 在 \(j\) 的路徑上,則 \(i\) 必須在 \(j\) 前面,如果 \(t_i\) 在 \(j\) 的路徑上,則 \(i\) 必須在 \(j\) 后面。
經典拓撲序建圖,倍增優化建圖即可,時空復雜度 \(\mathcal{O}((n + m)\log n)\)。
#define N 120005
#define M 4400005
int n, m, s[N], t[N], d[N], in[M], f[N][17], u[N][17], v[N][17], idx, T, dfn[N], tot, sz[N];
vector<int>e[N], w[M]; queue<int>q;
void dfs(int x,int fa){
d[x] = d[f[x][0] = fa] + 1, dfn[x] = ++tot, sz[x] = 1;
u[x][0] = ++idx, v[x][0] = ++idx;
rp(i, T){
f[x][i] = f[f[x][i - 1]][i - 1];
u[x][i] = ++idx;
w[u[x][i - 1]].pb(idx); if(f[x][i - 1])w[u[f[x][i - 1]][i - 1]].pb(idx);
v[x][i] = ++idx;
w[idx].pb(v[x][i - 1]); if(f[x][i - 1])w[idx].pb(v[f[x][i - 1]][i - 1]);
}
go(y, e[x])if(y != fa)dfs(y, x), sz[x] += sz[y];
}
void ins(int id,int x,int y){
if(dfn[x] <= dfn[y] && dfn[x] + sz[x] > dfn[y]){
pre(i, T, 0)if(d[f[y][i]] >= d[x])w[u[y][i]].pb(id), y = f[y][i];
}
else{
x = f[x][0];
if(d[x] < d[y])swap(x, y);
pre(i, T, 0)if(d[f[x][i]] >= d[y])w[u[x][i]].pb(id), x = f[x][i];
if(x == y)w[u[x][0]].pb(id);
else{
pre(i, T, 0)if(f[x][i] != f[y][i])
w[u[x][i]].pb(id), w[u[y][i]].pb(id), x = f[x][i], y = f[y][i];
w[u[x][0]].pb(id), w[u[y][1]].pb(id);
}
}
}
void ins_(int id,int x,int y){
if(dfn[y] <= dfn[x] && dfn[y] + sz[y] > dfn[x]){
pre(i, T, 0)if(d[f[x][i]] >= d[y])w[id].pb(v[x][i]), x = f[x][i];
}
else{
y = f[y][0];
if(d[x] < d[y])swap(x, y);
pre(i, T, 0)if(d[f[x][i]] >= d[y])w[id].pb(v[x][i]), x = f[x][i];
if(x == y)w[id].pb(v[x][0]);
else{
pre(i, T, 0)if(f[x][i] != f[y][i])
w[id].pb(v[x][i]), w[id].pb(v[y][i]), x = f[x][i], y = f[y][i];
w[id].pb(v[x][0]), w[id].pb(v[y][1]);
}
}
}
void solve(){
read(n), T = log2(n), tot = 0;
rp(i, n)e[i].clear();
rp(i, idx)w[i].clear(), in[i] = 0;
rp(i, n - 1){
int x, y; read(x, y);
e[x].pb(y), e[y].pb(x);
}read(m), idx = m;
dfs(1, 0);
rp(i, m){
int x, y; read(x, y);
w[i].pb(u[x][0]), w[v[y][0]].pb(i);
ins(i, x, y), ins_(i, x, y);
}
int sz = 0;
rp(i, idx)go(x, w[i])in[x]++;
rp(i, idx)if(!in[i])q.push(i);
while(!q.empty()){
int x = q.front(); q.pop(), sz += x <= m;
go(y, w[x]){
in[y]--; if(!in[y])q.push(y);
}
}if(sz == m)puts("Yes"); else puts("No");
}
int main() {
int T; read(T);
while(T--)solve();
return 0;
}
T2:給定 \(n \times m\) 的網格圖,第 \(i\) 行權值為 \(a_i\),第 \(j\) 列權值為 \(b_j\),每次只能向右或者向下移動,求 \((1,1)\to (n,m)\) 的最短路。
對於 Sub1 直接 \(NM\) 跑 DP即可。
對於 Sub2,對於 \(a_i\),如果前后都有數 \(\le a_i\),那么 \(i\) 一定用不上。所以優化后圖的規模是 \(2000\times 2000\),可以通過。
對於 Sub3,我們需要觀察一些性質。
如果我們只經過一次轉彎從 \((i,j) \to (x,y)\),有兩種路可以選擇,分別是 \((i,j)\to (i,y)\to (x,y)\) 和 \((i,j) \to (x,j) \to (x,y)\)。
這兩條路對應的大小分別是 \((y-j)a_i + (x-i)b_y\) 和 \((x-i)b_j + (y-j)a_x\)。
我們比較並化簡一下可以得到 \(\dfrac{a_x - a_i}{x - i} >\dfrac{b_y - b_j}{y - j}\) 時選擇第一條路更優,否則選擇第二條路。
想必到這里已經很清楚了,我們的答案就是進行若干次這樣的選擇。而上面的式子顯然時比較斜率,斜率越小越好。我們先對 \(a,b\) 求出下凸包,不在凸包上的一定存些斜率更小的兩個點可以替代,然后合並兩個凸包即可。
#define N 100005
int n, m, p[N], q[N], l, r; LL a[N], b[N];
int main() {
read(n, m);
rp(i, n)read(a[i]);
rp(i, m)read(b[i]);
p[l = 1] = q[r = 1] = 1;
rep(i, 2, n){
while(l > 1 && (a[i] - a[p[l]]) * (p[l] - p[l - 1]) <= (a[p[l]] - a[p[l - 1]]) * (i - p[l]))l--;
p[++l] = i;
}
rep(i, 2, m){
while(r > 1 && (b[i] - b[q[r]]) * (q[r] - q[r - 1]) <= (b[q[r]] - b[q[r - 1]]) * (i - q[r]))r--;
q[++r] = i;
}
int x = 1, y = 1, s = 1, t = 1; LL ans = 0;
while(x < n || y < m){
if(s == l || (t != r && (a[p[s + 1]] - a[p[s]]) * (q[t + 1] - q[t]) > (b[q[t + 1]] - b[q[t]]) * (p[s + 1] - p[s])))
ans += (q[t + 1] - q[t]) * a[x], y = q[++t];
else ans += (p[s + 1] - p[s]) * b[y], x = p[++s];
}
printf("%lld\n", ans);
return 0;
}
T3:求有多少個長度為 \(N\) 的字符串滿足 \(M\) 個條件,每個條件形如 \((i,j)\) 表示刪除字符 \(s_i\) 后剩下串大於等於刪除 \(s_j\) 后剩下的串。
觀察一下,我們令 \(t_i\) 表示 \(s_i\) 和 \(s_{i + 1}\) 的大小關系,那么限制 \((i,j) (i > j)\) 表示區間 \([i,j)\) 中的第一個不等號是 \(>\),\((i,j)(i < j)\) 表示第一個不等號是 \(<\)。
設計 DP,用 \(f_{i,j}\) 表示前 \(i\) 個字符滿足所有 \((l,r),l \le i\land i\le r\) 的限制條件,以 \(j\) 結尾的方案。前綴和優化轉移即可,時間復雜度 \(\mathcal{O}(N|S|+M\log M)\)。
#define N 500005
int n, m, f[N][26], up[N][26], dn[N][26];
vector<int>u[N], v[N];
multiset<int>s, t;
int main() {
read(n, m);
rp(i, m){
int x, y;
read(x, y);
if(x < y)v[x + 1].pb(x), v[y + 1].pb(-x);
else u[y + 1].pb(y), u[x + 1].pb(-y);
}int ans = 26;
rep(i, 2, n){
go(x, u[i])
if(x > 0)s.insert(x); else s.erase(s.find(-x));
go(x, v[i])
if(x > 0)t.insert(x); else t.erase(t.find(-x));
int sl = 2, sr = 2;
if(!s.empty())sl = *s.rbegin() + 1;
if(!t.empty())sr = *t.rbegin() + 1;
if(sl < i)rep(r, 0, 24)ad(f[i][r], dn[i - 1][r + 1]), su(f[i][r], dn[sl - 1][r + 1]);
if(sr < i)rp(r, 25)ad(f[i][r], up[i - 1][r - 1]), su(f[i][r], up[sr - 1][r - 1]);
if(s.empty())rep(r, 0, 25)ad(f[i][r], 25 - r);
if(t.empty())rep(r, 0, 25)ad(f[i][r], r);
rep(j, 0, 25)ad(ans, f[i][j]);
up[i][0] = f[i][0]; rp(j, 25)up[i][j] = (up[i][j - 1] + f[i][j]) % P;
dn[i][25] = f[i][25]; pre(j, 24, 0)dn[i][j] = (dn[i][j + 1] + f[i][j]) % P;
rep(j, 0, 25)ad(up[i][j], up[i - 1][j]), ad(dn[i][j], dn[i - 1][j]);
}cout << ans << endl;
return 0;
}
Day2
T1:有三個操作,花費 \(A\) 的代價在當前串結尾加一個字符,花費 \(B\) 的代價將當前串剪切進剪切板(注意是剪切不是復制),花費 \(C\) 的代價在當前串結尾粘貼。
我們定義 \(f_{l,r}\) 表示區間 \([l,r]\) 的答案,直接按題意 DP 的復雜度是 \(\mathcal{O}(N^4)\) 的。
具體轉移有兩種,\(f_{l,r} = A + f_{l, r- 1}\),和枚舉 \(k\),用 \(s_{[l,k]}\) 表示這次剪切的內容,然后貪心在 \(s_{[l,r]}\) 中刪去最多個不重復的子串 \(s_{[l,k]}\)。
用字符串哈希可以快速判斷。為減少狀態,我們添加一個轉移 \(f_{l,r} = A + f_{l + 1,r}\)。然后進行剪切操作當且僅當 \(s_{[l,p]} = s_{[q, r]}\)。這樣預處理 KMP,每次跳 next 指針可以保證一定是合法的剪切。對於兩個相同的子串我們只用計算一次,每跳一次 next 必然存在兩個相同的子串,所以最多轉移 \(\mathcal{O}(N^2)\) 次。
問題在於快速求出區間 \([l,r]\) 中最多能選多少個不重復的 \([l,p]\),可以預處理 \(g_{l,r,k}\) 表示和 \(s_{[l,r]}\) 相同的不重復的后面 \(2^k\) 個子串左端點。倍增一下即可。時間復雜度 \(\mathcal{O}(N^2\log N)\)。
#define N 2505
typedef unsigned long long ull;
int n, nxt[N][N], ste[12][N][N]; char s[N]; LL A, B, C, f[N][N]; ull h[N], pw[N];
#define g(l, r) (h[r] - h[l - 1] * pw[r - (l) + 1])
#define M 19989997
LL g[M + 500]; ull ky[M + 500];
pair<ull, int>b[N];
int main() {
read(n);
scanf("%s", s + 1);
rp(l, n){
int j = 0;
rep(r, l + 1, n){
while(j && s[r] != s[l + j])j = nxt[l][l + j - 1];
if(s[r] == s[l + j])j++;
nxt[l][r] = j;
}
}
pw[0] = 1; rp(i, n)h[i] = h[i - 1] * R + s[i], pw[i] = pw[i - 1] * R;
rp(i, n){
int T = 0;
rp(j, n - i + 1)b[++T] = mp(g(j, j + i - 1), j);
sort(b + 1, b + T + 1, [](Pr x, Pr y){return x.fi != y.fi ? x.fi < y.fi : x.se > y.se;});
int k = 1;
rp(j, T){
while(k < j && b[k].fi != b[j].fi)k++;
while(k < j && b[j].se + i <= b[k + 1].se)k++;
if(k < j && b[j].se + i <= b[k].se)ste[0][b[j].se][i] = b[k].se;
}
}
rp(k, 11)rp(j, n)rp(i, n - j + 1)ste[k][i][j] = ste[k - 1][ste[k - 1][i][j]][j];
read(A, B, C); int tem = 0;
rp(r, n)pr(l, r){
if(l == r){f[l][r] = A; continue;}
ull v = g(l, r), now = v % M;
while(ky[now] && ky[now] != v)now++;
if(ky[now]){f[l][r] = g[now]; continue;}
ky[now] = v;
LL cur = min(f[l + 1][r], f[l][r - 1]) + A;
int t = (r - l + 1) / 2, k = nxt[l][r];
while(k && k > t)k = nxt[l][l + k - 1];
for(; k; k = nxt[l][l + k - 1]){
int cnt = r - l + 1, sum = 2, p = l;
pre(i, 11, 0)if(ste[i][p][k] && ste[i][p][k] + k - 1 <= r - k)p = ste[i][p][k], sum += 1 << i;
cnt -= sum * k;
cmn(cur, f[l][l + k - 1] + B + sum * C + cnt * A);
}
assert(tem <= 100000000);
f[l][r] = g[now] = cur;
}
printf("%lld\n", f[1][n]);
return 0;
}
T2:通信題,給 Alice 一顆樹,Alice 將樹上的點重新標號,標號區間為 \([1,2N+19]\)。Bob 現在得知兩個點的標號,告訴 Alice 20bits 的信息,Alice 反饋給 Bob \(X\) bits 的信息,Bob 要利用反饋的信息算出這兩個點的距離,X 越小得分越高。
20 bits的信息顯然不能直接表示兩個點 \(x,y\),所以考慮將樹上的點分組,然后傳組號。
不難想到樹分塊,令 \(B=10\),每塊大小 \(\in [10,19]\)。最多 \(1000\) 塊正好 10 bits 可以表示一個組號。
知道組號后把兩個塊內子樹和兩個塊之間的距離反饋回去。所以我們對樹但 dfs 序重標號,然后將 dfs 時入棧和出棧操作作為 \(0/1\) 反饋回去。這樣就可以還原出一棵樹。
寫的好能拿 \(70\) 分左右,滿分待補。
T3:給定若干三元組 \((a,b,c)\),求三元組的三元組 \((A,B,C)\) 滿足 \(Aa > Ba,Ca\),\(Bb > Ab,Cb\),\(Cc>Ac,Bc\),使 \(Aa+Bb+Cc\) 最大。
考慮按 \(c\) 排序,對於每個三元組 \(X\),將 \(c\) 更小的三元組加入集合 \(S\)。我們只用找到集合中最大的 \(Aa >Ba\) 且 \(Ab < Bb\) 的三元組即可。直接 set 維護即可,時間復雜度 \(\mathcal{O}(N\log N)\)。
#define N 150005
int n, ans = ~0, sa, sb;
struct node{
int a, b, c;
bool operator<(const node o)const{return c < o.c;}
}a[N];
set<Pr>s;
void ins(int x,int y){
if(x <= sa && y <= sb)return;
if(x < sa)sb = y;
else if(y < sb)sa = x;
while(true){
auto cur = s.lower_bound(mp(x + 1, 0));
if(cur == s.end())break;
if((*cur).se < y)cmx(sa, (*cur).fi), cmx(sb, y), s.erase(cur);
else break;
}
while(true){
auto cur = s.lower_bound(mp(x, 0));
if(cur == s.begin())break;
cur--;
if((*cur).se > y)cmx(sa, x), cmx(sb, (*cur).se), s.erase(cur);
else break;
}
s.insert(mp(x, y));
while(!s.empty()){
auto cur = s.begin();
if((*cur).fi <= sa && (*cur).se <= sb)s.erase(cur);
else break;
}
}
void calc(int l,int r){
rep(i, l, r)if(sa > a[i].a && sb > a[i].b)cmx(ans, sa + sb + a[i].c);
rep(i, l, r)ins(a[i].a, a[i].b);
}
int main() {
read(n);
rp(i, n)read(a[i].a, a[i].b, a[i].c);
sort(a + 1, a + n + 1);
for(int i = 1; i <= n;){
int j = i;
while(a[j].c == a[i].c)j++;
calc(i, j - 1), i = j;
}printf("%d\n", ans);
return 0;
}
Day3
T1:通信題,Alice 要告訴 Bob 一個 \(<10^{18}\) 的數,Alice 給出兩個等長的 \(0/1\) 串 \(s,t\),\(s,t\) 會被歸並成一個新串並告訴 Bob,Bob 要還原出這個數,歸並策略是任意的(不隨機)。
這題確實非常難以下手,這個歸並就相當於洗撲克的疊牌,洗了后原來的信息就沒了。
由於數 \(<10^{18}\),意味着我們需要保留 \(60\) 個有效位。
對於 \(s,t\),考慮犧牲一個串成全另一個串。首先我們讓 \(0/1\) 串等價於 \(-1/1\) 串,那么我們讓 \(t = \{1,-1,1,-1,\cdots\}\),那么對於 \(t\) 的前綴和是 \(0/1\)。
那么對於串 \(s\),我們將每一位重復 \(3\) 遍。對於歸並后的串求前綴和 \(u\),顯然 \(u_i = 3k + \{-1/0/1\}\) 的 \(k\) 是唯一的,后面的 \(\{-1/0/1\}\) 是 \(t\) 串帶來的波動,而 \(k\) 則是有效信息,如果 \(k\) 增了說明該有效位是 \(1\),否則是 \(0\)。
所以需要 \(180\) 個 bits 能得到 \(80\) 分。滿分待補。
T2: 給定一棵樹,支持兩個操作。
1 x y z
表示到點 \(x\) 的距離 \(\le y\) 的點都 \(\times z\),2 x
表示查詢點 \(x\) 的權值,\(y\le 40\)。
定義 \(f_{i,j}\) 表示將點 \(i\) 子樹中距離 \(\le j\) 的點都乘 \(f_{i,j}\)。每次修改和詢問最多和最多 \(40\) 個祖先有關。直接模擬復雜度是 \(nd^2\),差分一下可以做到 \(\mathcal{O}(nd)\)。
#define N 200005
int n, u[N], fa[N], w[N], t, P; vector<int>e[N];
void dfs(int x,int f){
fa[x] = f; go(y, e[x])if(y != f)dfs(y, x);
}
int f[41][N], c[10];
int main() {
read(n, P);
rp(i, n - 1){
int x, y; read(x, y);
e[x].pb(y), e[y].pb(x);
}
srand(time(0));
dfs(rand() % n + 1, 0);
rp(i, n){
read(u[i]);
rep(j, 0, 40)f[j][i] = 1;
}
int T; read(T);
while(T--){
int op, x, y; LL z; read(op, x);
if(1 == op){
read(y, z);
int d = y, r = x;
while(r && d >= 0){
if(fa[r] && d > 1)
f[d][r] = f[d][r] * z % P, f[d - 1][r] = f[d - 1][r] * z % P;
else rep(i, 0, d)f[i][r] = f[i][r] * z % P;
d--, r = fa[r];
}
}
else{
LL cur = u[x];
y = x; int d = 40;
while(y && d >= 0){
cur = cur * f[40 - d][y] % P, d--, y = fa[y];
}printf("%lld\n", cur);
}
}
return 0;
}
T3:
待補
Day4
T1:交互題。\(n\) 種顏色,每種顏色 \(m\) 個球。\(n\) 個不同顏色的球可以組成一組。每次可以詢問一個集合 \(S\) 中的球最多能組成多少組。現在要將所有球分成 \(m\) 組,最多詢問 \(50000\) 次,\(n\le 400, m\le 25\)。
從左向右一次詢問區間 \([1,i]\),第一個返回 \(1\) 的位置 \(x\) 一定是該顏色的第一次出現。
將 \(x\) 加入當前集合 \(ans\),然后從后向前掃,如果當前掃到位置 \(i\),區間 \([1,i]\) 加上 \(ans\) 集合里的數不能組成一組,則把 \(i + 1\) 加入 \(ans\) 集合。
掃一個來回后 \(ans\) 集合就是一個合法組。直接掃需要詢問 \(nm^2\) 次,顯然是無法通過的。
我們可以優化一下,向右掃的過程我們可以直接二分答案。向左掃雖然也可以二分,但是需要二分的次數太多反而是負優化,我們類似循環展開每次向前跳 \(3\) 個位置即可。這樣期望的詢問次數 \(m\log nm + \dfrac{nm^2}{6}\) 勉強可以通過。
但是如果所有球是有序的會被卡成 \(\dfrac{nm^2}{3}\),所以我們開始前對所有標號隨機打亂,這樣無論數據如何對於我們都是隨機均勻的。
mt19937 rd(765234);
#define maxn 10005
int p[maxn], a[maxn], b[maxn], t;
vector<int>c;
int ask(int x){
vector<int>u = c;
rp(i, x)u.pb(p[a[i]]);
return Query(u);
}
void Solve(int n,int m){
rp(i, n * m)p[i] = i, a[i] = i;
shuffle(p + 1, p + n * m + 1, rd);
t = n * m;
rp(i, m){
if(i == m){
rp(j, t)c.pb(p[a[j]]);
Answer(c); return ;
}
int l = n, r = min(t, 4000), w = 0;
while(l <= r){
int mid = (l + r) >> 1;
if(ask(mid))w = mid, r = mid - 1;
else l = mid + 1;
}
c.pb(p[a[w]]), a[w--] = 0;
while(si(c) < n){
if(w < 5){
if(!ask(w - 1)){
c.pb(p[a[w]]), a[w] = 0;
}w--;
}
else{
if(ask(w - 3))w -= 3;
else{
if(!ask(w - 1)){
c.pb(p[a[w]]), a[w] = 0, w--;
}
else if(!ask(w - 2)){
c.pb(p[a[w - 1]]), a[w - 1] = 0, w -= 2;
}
else c.pb(p[a[w - 2]]), a[w - 2] = 0, w -= 3;
}
}
}
Answer(c), c.clear();
int T = 0;
rp(j, t)if(a[j])b[++T] = a[j];
t = T; rp(j, t)a[j] = b[j];
}
}
T2:給定一個序列,支持單點修改和區間查詢。每個數可以吞並相鄰的不大於它的數,變成兩數之和。每次詢問區間內有多少個數可以吞下整個區間。
最大數一定可以,我們以最大值為分界,如果左邊之和大於最大值,則遞歸統計左邊,對右邊同理,類似於笛卡爾樹。直接做是 \(\mathcal{O}(QN)\) 可以得到 25 分。如果沒有修改操作,我們可以從區間左端點每次跳到右邊第一個比自己大的位置,一直到區間最大值,並統計答案,倍增可以優化至 \(\mathcal{O}(Q\log N)\),可以得到 \(48\) 分。
其余待補。
T3:給定帶權無向圖,每次詢問 \(X\),表示如果所有邊權變為 \(|X-w_i|\) 的最小生成樹。
將所有邊按照邊權排序,求出前綴最小生成樹和后綴生成樹。對於一次詢問 \(X\) 相當於合並前后兩顆樹得到新的最小生成樹。
直接做時間復雜度 \(\mathcal{O}((q+m)n\alpha(n))\),只有 \(42\) 分。
\(qn\alpha(n)\) 太大了考慮優化掉。
對於預處理的過程,從小到大每次加入一條邊,如果成環則刪除一條邊。
如果沒有刪邊,那么對於所有 \(x\),都要選擇當前的邊。否則我們設刪除的邊為 \(p\),當前邊為 \(q\),那么 \(p\) 一定是 \(q\) 對應樹的路徑最大的邊,邊權 \(<p\) 的其他邊一定不在路徑上,所以當 \(x\le \dfrac{w_p+w_q}{2}\) 時選擇 \(p\),否則選擇 \(q\)。
直接做是 \(\mathcal{O}(mn\alpha(n) + q)\),由於每次是加一條邊並刪一條邊,可以直接在樹上找最大的邊,復雜度 \(\mathcal{O}(mn+q)\),也可以用 LCT 維護做到 \(\mathcal{O}(m(\log m+\log n) + q)\)。
#define N 505
#define M 100005
int n, m, fa[N];
int get(int x){return fa[x] == x ? x : fa[x] = get(fa[x]);}
struct node{
int u, v, w;
bool operator<(const node o)const{return w < o.w;}
}e[M], u[N], v[N];
vector<node>a;
int main() {
read(n, m);
rp(i, m)read(e[i].u, e[i].v, e[i].w);
sort(e + 1, e + m + 1);
int t = 0;
rp(i, m){
rp(i, n)fa[i] = i;
fa[e[i].u] = e[i].v;
int p = 0, dl = 0;
v[++p] = e[i];
rp(i, t){
if(dl)v[++p] = u[i];
else{
int x = get(u[i].u), y = get(u[i].v);
if(x != y)fa[x] = y, v[++p] = u[i];
else dl = i;
}
}
if(!dl)a.pb(node{-1, e[i].w, 0}), a.pb(node{2, -2 * e[i].w, e[i].w});
else {
a.pb(node{-1, u[dl].w, (e[i].w + u[dl].w + 1) >> 1});
a.pb(node{-1, e[i].w, (e[i].w + u[dl].w + 1) >> 1});
a.pb(node{2, -2 * e[i].w, e[i].w});
}
t = p; rp(i, t)u[i] = v[i];
}
sort(a.begin(), a.end());
int j = 0, sz = si(a); LL l = 0, r = 0;
int T; read(T);
while(T--){
int x; read(x);
while(j < sz && a[j].w <= x)l += a[j].u, r += a[j].v, j++;
printf("%lld\n", l * x + r);
}
return 0;
}