@NOIP2018 - [email protected] 保衛王國
目錄
【部落格搬運 * 6】
@題目描述@
Z 國有n座城市,n−1 條雙向道路,每條雙向道路連線兩座城市,且任意兩座城市 都能通過若干條道路相互到達。
Z 國的國防部長小 Z 要在城市中駐紮軍隊。駐紮軍隊需要滿足如下幾個條件:
(1)一座城市可以駐紮一支軍隊,也可以不駐紮軍隊。
(2)由道路直接連線的兩座城市中至少要有一座城市駐紮軍隊。
(3)在城市裡駐紮軍隊會產生花費,在編號為i的城市中駐紮軍隊的花費是p。
小 Z 很快就規劃出了一種駐紮軍隊的方案,使總花費最小。但是國王又給小 Z 提出了 m 個要求,每個要求規定了其中兩座城市是否駐紮軍隊。小 Z 需要針對每個要求逐一給出回答。
具體而言,如果國王提出的第 j 個要求能夠滿足上述駐紮條件(不需要考慮第 j 個要求之外的其它要求),則需要給出在此要求前提下駐紮軍隊的最小開銷。如果國王提出的第 j 個要求無法滿足,則需要輸出−1(1≤j≤m)。現在請你來幫助小 Z。
輸入
第 1 行包含兩個正整數 n,m 和一個字串 type,分別表示城市數、要求數和資料型別。type 是一個由大寫字母 A,B 或 C 和一個數字 1,2,3 組成的字串。它可以幫助你獲得部分分。你可能不需要用到這個引數。
第 2 行 n 個整數 pi,表示編號 i 城市中駐紮軍隊的花費。
接下來 n−1 行,每行兩個正整數u,v,表示有一條 u 到 v 的雙向道路。
接下來 m 行,第 j 行四個整數 a,x,b,y(a≠b),表示第 j 個要求是在城市 a 駐紮 x 支軍隊, 在城市 b 駐紮 y 支軍隊。其中,x 、y 的取值只有 0 或 1:若 x 為 0,表示城市 a 不得駐紮軍隊,若 x 為 1,表示城市 a 必須駐紮軍隊;若 y 為 0,表示城市 b 不得駐紮軍隊, 若 y 為 1,表示城市 b 必須駐紮軍隊。
輸入檔案中每一行相鄰的兩個資料之間均用一個空格分隔。
輸出
輸出共 m 行,每行包含 1 個整數,第 j 行表示在滿足國王第 j 個要求時的最小開銷, 如果無法滿足國王的第 j 個要求,則該行輸出 −1。
輸入樣例#1
5 3 C3
2 4 1 3 9
1 5
5 2
5 3
3 4
1 0 3 0
2 1 3 1
1 0 5 0
輸出樣例#1
12
7
-1
樣例解釋
對於第一個要求,在 4 號和 5 號城市駐紮軍隊時開銷最小。
對於第二個要求,在 1 號、2 號、3 號城市駐紮軍隊時開銷最小。
第三個要求是無法滿足的,因為在 1 號、5 號城市都不駐紮軍隊就意味著由道路直接連線的兩座城市中都沒有駐紮軍隊。
資料規模與約定
對於 100% 的資料,n,m ≤ 100000,1 ≤ pi ≤ 100000。
@題解@
傻逼倍增題,我竟然不會做,實在是太弱了。
假如不加限制就是一個很簡單的 dp。
狀態定義為 dp[i][0/1] 表示 i 結點駐紮/不駐紮時,i 為根的子樹的最小值。
於是:
\[dp[i][0]=\sum_{j=1}^{j\in\ i's \ child}dp[j][1]\\dp[i][1]=p_i+\sum_{j=1}^{j\in\ i's \ child}min(dp[j][0], dp[j][1])\]
基於此,我們最簡單最粗暴的想法肯定就是:寫動態 dp。假如結點 a 一定駐紮,就修改 dp[a][0] = INF;否則修改 dp[a][1] = INF。
沒錯考場上我也是這麼想的……然後發現自己寫不來……
但其實這道題不需要寫動態 dp,因為沒有修改操作,而只有查詢操作。
我們再看看我們按照動態 dp 的想法而實現的暴力:如果修改結點 a, b 的權,就從 a, b 開始往上爬父親,更新沿途的 dp 值。
可不可以用某種方法加速這個爬父親的過程呢?
當然可以。我們利用我們求 LCA 用的方法:樹上倍增。
具體來說,令\(fa[i][j]\):i 的第 2^j 個父親;令 \(f[i][j][0/1][0/1]\) :當固定 i 駐紮/不駐紮時,i 的第 2^j 個父親的 dp[0/1] 的最小值。
如果已知 i 的第 2^(j-1) 個父親的資訊,可以通過列舉 i 的 2^(j-1) 個父親的駐紮情況,推匯出 i 的第 2^j 個父親的資訊。假如令 k = i 的 2^(j-1) 個父親,舉一個轉移的例子:
\[f[i][j][0][0] = \min(f[k][j-1][0][0]-dp[k][0]+f[i][j-1][0][0], f[k][j-1][1][0]-dp[k][1]+f[i][j-1][0][1])\]
min 的前半部分表示 i 的第 2^(j-1) 個父親不駐紮,後半部分表示 i 的第 2^(j-1) 個父親要駐紮。大體來說就是:總答案-不受限制的貢獻+受限制的貢獻。
邊界情況 f[i][0][0/1][0/1] 的求解可以直接藉助 dp 陣列來搞。
假如詢問 a 和 b,我們可以先跳到兩者的 LCA 再繼續跳到根,求出根的值。因為由上面我們可以 f 是可以合併相互之間的資訊的,所以只需要仿照上面的方法就可以實現倍增跳父親。
具體實現細節可看看程式碼。
@程式碼@
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 100000;
const ll INF = (1LL<<60);
struct edge{
int to;
edge *nxt;
}edges[2*MAXN + 5], *adj[MAXN + 5], *ecnt=&edges[0];
void addedge(int u, int v) {
edge *p=(++ecnt);
p->to = v, p->nxt = adj[u], adj[u] = p;
p = (++ecnt);
p->to = u, p->nxt = adj[v], adj[v] = p;
}
int a, x, b, y;
bool g[MAXN + 5];
int p[MAXN + 5], dep[MAXN + 5], fa[MAXN + 5][20];
ll dp[MAXN + 5][2], f[MAXN + 5][20][2][2];
void dfs1(int rt, int pre) {
dep[rt] = dep[pre] + 1;
dp[rt][0] = 0, dp[rt][1] = p[rt];
for(edge *p=adj[rt];p!=NULL;p=p->nxt) {
if( p->to == pre ) continue;
dfs1(p->to, rt);
dp[rt][0] = (dp[rt][0] + dp[p->to][1]);
dp[rt][1] = (dp[rt][1] + min(dp[p->to][0], dp[p->to][1]));
}
}
void dfs2(int rt, int pre) {
fa[rt][0] = pre;
f[rt][0][0][0] = INF;
f[rt][0][1][0] = dp[pre][0];
f[rt][0][0][1] = dp[pre][1] - min(dp[rt][0], dp[rt][1]) + dp[rt][0];
f[rt][0][1][1] = dp[pre][1] - min(dp[rt][0], dp[rt][1]) + dp[rt][1];
for(int i=1;i<20;i++) {
int anc = fa[rt][i-1];
fa[rt][i] = fa[anc][i-1];
f[rt][i][0][0] = min(f[anc][i-1][0][0]-dp[anc][0]+f[rt][i-1][0][0], f[anc][i-1][1][0]-dp[anc][1]+f[rt][i-1][0][1]);
f[rt][i][1][0] = min(f[anc][i-1][0][0]-dp[anc][0]+f[rt][i-1][1][0], f[anc][i-1][1][0]-dp[anc][1]+f[rt][i-1][1][1]);
f[rt][i][0][1] = min(f[anc][i-1][0][1]-dp[anc][0]+f[rt][i-1][0][0], f[anc][i-1][1][1]-dp[anc][1]+f[rt][i-1][0][1]);
f[rt][i][1][1] = min(f[anc][i-1][0][1]-dp[anc][0]+f[rt][i-1][1][0], f[anc][i-1][1][1]-dp[anc][1]+f[rt][i-1][1][1]);
}
for(edge *p=adj[rt];p!=NULL;p=p->nxt) {
if( p->to == pre ) continue;
dfs2(p->to, rt);
}
}
char type[3];
int main() {
int n, m;
scanf("%d%d%s", &n, &m, type);
for(int i=1;i<=n;i++)
scanf("%d", &p[i]);
for(int i=1;i<n;i++) {
int u, v;
scanf("%d%d", &u, &v);
addedge(u, v);
}
dfs1(1, 0); dfs2(1, 0);
for(int i=1;i<=m;i++) {
scanf("%d%d%d%d", &a, &x, &b, &y);
if( x == 0 && y == 0 ) {
if( fa[a][0] == b || fa[b][0] == a ) {
printf("-1\n");
continue;
}
}
if( dep[a] < dep[b] )
swap(a, b), swap(x, y);
ll v1, v2, v3, v4;
v1 = x? dp[a][1] : INF;
v2 = x? INF : dp[a][0];
v3 = y? dp[b][1] : INF;
v4 = y? INF : dp[b][0];
for(int i=19;i>=0;i--) {
if( dep[fa[a][i]] >= dep[b] ) {
ll tmp1, tmp2;
tmp1 = min(f[a][i][1][1] - dp[a][1] + v1, f[a][i][0][1] - dp[a][0] + v2);
tmp2 = min(f[a][i][1][0] - dp[a][1] + v1, f[a][i][0][0] - dp[a][0] + v2);
v1 = tmp1, v2 = tmp2;
a = fa[a][i];
}
}
if( a == b ) {
if( y ) v2 = INF;
else v1 = INF;
}
else {
for(int i=19;i>=0;i--)
if( fa[a][i] != fa[b][i] ) {
ll tmp1, tmp2, tmp3, tmp4;
tmp1 = min(f[a][i][1][1] - dp[a][1] + v1, f[a][i][0][1] - dp[a][0] + v2);
tmp2 = min(f[a][i][1][0] - dp[a][1] + v1, f[a][i][0][0] - dp[a][0] + v2);
tmp3 = min(f[b][i][1][1] - dp[b][1] + v3, f[b][i][0][1] - dp[b][0] + v4);
tmp4 = min(f[b][i][1][0] - dp[b][1] + v3, f[b][i][0][0] - dp[b][0] + v4);
v1 = tmp1, v2 = tmp2, v3 = tmp3, v4 = tmp4;
a = fa[a][i], b = fa[b][i];
}
int pr = fa[a][0];
ll tmp1, tmp2, tmp3 = dp[pr][1]-min(dp[b][0], dp[b][1])-min(dp[a][0], dp[a][1]), tmp4 = dp[pr][0]-dp[a][1]-dp[b][1];
tmp1 = tmp3+min(v1, v2)+min(v3, v4);
tmp2 = tmp4+v1+v3;
v1 = tmp1, v2 = tmp2;
a = pr;
}
for(int i=19;i>=0;i--)
if( dep[fa[a][i]] >= dep[1] ) {
ll tmp1, tmp2;
tmp1 = min(f[a][i][1][1] - dp[a][1] + v1, f[a][i][0][1] - dp[a][0] + v2);
tmp2 = min(f[a][i][1][0] - dp[a][1] + v1, f[a][i][0][0] - dp[a][0] + v2);
v1 = tmp1, v2 = tmp2;
a = fa[a][i];
}
printf("%lld\n", min(v1, v2));
}
}