深受啓發的題解;ios
DP的集大成者。c++
首先,這道題最原始的問題就是經典的最大獨立集問題。沒有上司的舞會。數組
咱們考慮DP:設\(dp(u,0)\)表明節點\(u\)沒被選上,\(dp(u,1)\)表明結點\(u\)被選上,而後轉移。spa
時間複雜度爲\(O(n)\);code
那麼對於本題的\(44pts\),咱們只須要每次暴力修改點值,而後跑一遍最大獨立集的模板,便可以輕鬆得到了。blog
接下來,咱們先將問題簡單化。圖片
咱們能夠先考慮每次修改一個點的時候該怎麼作;ip
容易看到,對於以該點(不妨設爲\(u\))爲根的子樹,給定狀態(指令:選仍是不選,不妨設爲\(state\))對應的dp值就是這棵子樹對答案的影響。換言之,對於整棵子樹,咱們不須要重複計算它們的dp值。咱們只須要將\(dp(u,state)\)做爲答案。ci
那麼,對於點\(u\)的祖先節點如何統計它們對答案的貢獻啊?get
也就是說,咱們直接將點\(u\)做爲整棵樹新的根結點,再按照咱們剛剛討論過的「經典最大獨立集」的作法進行求解。
這個方法針對於單次修改徹底能接受,但是\(m\)次修改呢?
咱們考慮如下方法:
能夠看到,這個作法其實就相似於換根dp的思想(或就是((()。
如今,咱們拓展——每次修改兩個點的狀態,該怎麼辦。
考慮到剛剛咱們是怎麼作的。能夠發現,對於被修改的兩個點,不妨設爲\(u\)和\(v\),\(u\)、\(v\)和\(path(u,v)\)(\(u\)到\(v\)的簡單路徑所構成的點集)能夠當作一個「廣義節點」。
類比於剛剛的作法,咱們能夠留意:
將這些點聚合成一個「廣義節點」便可以按照上述作法解決。
既然如此,那麼「廣義節點」怎麼求?
咱們是單純把\(path(u,v)\)當成「廣義節點」,仍是將路徑上牽出來的子樹囊括其中?
選擇後者。
這是由於前者求解這些子樹時依賴於路徑上的每個點的選取狀態,而想要求解最小值只能枚舉。
咱們先考慮——樹退化爲一條鏈的狀況:\(u\)和\(v\)是鏈上的兩個端點。按照咱們以前的作法,直接將全部\(u\)和\(v\)路徑上的點所有縮掉。值得歡慶的是,這條路徑上沒有子樹。
咱們對於「廣義節點」外的結點直接跑最大獨立集,對內再來一個獨立集。
詳細地講,咱們在求解「廣義節點」的內部獨立集問題時,只不過和外面「絕緣」。換言之,咱們求解內部獨立集的時候,把它當作一個獨立的鏈來統計。
不過這樣的效率是極低的,咱們不妨用倍增的思想處理 內部矛盾。
定義\(dp(u,i,0/1,0/1)\)表明從\(u\)到\(2^i\)級祖先的最小值。
按照正常的倍增作法去作。而後對於一條鏈而言,咱們按二進制把它拆開來,一段一段看成求獨立集時一個個結點維護便可。(不清楚的看代碼)
咱們再考慮,當\(u\)是\(v\)的祖先時的狀況。
能夠看到,在這個問題裏面,與上一個子問題惟一有差別的地兒是:「廣義節點」路徑上是有子樹的。由此,在初始化時,咱們還要算上子樹的權值便可。(具體實現細節,能夠認真思考)。
最後,咱們將直接面對最通常的狀況,即當不存在一個節點是另外一個節點的祖先時,咱們該如何處理。
先抓住兩點的最近公共祖先(記做\(lca\))。咱們不難觀察到,假若\(lca\)選取狀態肯定了,以下面的圖片所示,那麼這就至關於跑了兩遍的「問題\(2\)」(\(u\)到\(lca\)和\(v\)到\(lca\))再加上\(lca\)上面的一大團東西。
但是問題是\(lca\)選取狀態不肯定。不要緊,咱們就枚舉它的「選取狀態」,分別對於每種選取狀態進行求解更新。
#include<iostream> #include<cstring> #include<cstdio> #include<vector> #include<cmath> #include<map> #define PII pair <int, int> #define MP make_pair #define RE register #define CLR(x, y) memset(x,y,sizeof x) #define FOR(i, x, y) for(RE int i=x;i<=y;++i) #define ROF(i, x, y) for(RE int i=x;i>=y;--i) using namespace std; typedef long long LL; const int MAXN = 1e5 + 5; const LL INF = 1e12; template <class T> void read(T &x) { bool mark = false; char ch = getchar(); for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') mark = true; for(x = 0; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + ch - '0'; if(mark) x = -x; } map <PII, bool> table; vector <int> G[MAXN]; int n, m, t; int F[MAXN][30] = {}, dep[MAXN] = {}; // F[u,i] -> u的2^i級祖先 dep[u] -> u到根節點的深度 T[u] -> u到根節點的距離的log值 LL dp1[MAXN][2] = {}, dp2[MAXN][2] = {}, dp[MAXN][30][2][2] = {}, p[MAXN] = {}; // dp1[u][0/1] -> 在u子樹中, 不選/選 u的最小值 dp2[u][0/1] -> 在整棵樹除了u子樹中, 不選/選 u的最小值 // 倍增預處理 void BFS() { int hh = 0, tt = 0, q[MAXN] = {}; int u, v; dep[1] = 1, q[tt ++] = 1; while(hh < tt) { u = q[hh ++]; for(RE int i = 0; i < G[u].size(); ++ i) { v = G[u][i]; if(dep[v]) continue; dep[v] = dep[u] + 1, F[v][0] = u; FOR(i, 1, t) F[v][i] = F[F[v][i - 1]][i - 1]; q[tt ++] = v; } } return; } // the prework of dp1[u, 0/1] void dfs_dp1(int u) { dp1[u][0] = 0, dp1[u][1] = p[u]; for(RE int i = 0; i < G[u].size(); ++ i) { int v = G[u][i]; if(v == F[u][0]) continue; dfs_dp1(v); dp1[u][0] += dp1[v][1], dp1[u][1] += min(dp1[v][0], dp1[v][1]); } return; } // the prework of dp2[u, 0/1] void dfs_dp2(int u)// @ { int v; for(RE int i = 0; i < G[u].size(); ++ i) { v = G[u][i]; if(v == F[u][0]) continue; dp2[v][0] = dp2[u][1] + dp1[u][1] - min(dp1[v][0], dp1[v][1]); dp2[v][1] = min(dp2[v][0], dp2[u][0] + dp1[u][0] - dp1[v][1]); dfs_dp2(v); } return; } // the prework of all of those dp arrays void dp_prework() { dfs_dp1(1), dfs_dp2(1); FOR(i, 1, n) FOR(j, 0, t) FOR(x, 0, 1) FOR(y, 0, 1) dp[i][j][x][y] = INF; FOR(i, 2, n) { int fa = F[i][0]; dp[i][0][0][1] = dp1[fa][1] - min(dp1[i][0], dp1[i][1]); dp[i][0][1][0] = dp1[fa][0] - dp1[i][1]; dp[i][0][1][1] = dp1[fa][1] - min(dp1[i][0], dp1[i][1]); } FOR(j, 1, t) { FOR(i, 1, n) { int anc = F[i][j - 1];// anc -> ancestor dp[i][j][0][0] = min(dp[i][j - 1][0][0] + dp[anc][j - 1][0][0], dp[i][j - 1][0][1] + dp[anc][j - 1][1][0]); dp[i][j][0][1] = min(dp[i][j - 1][0][0] + dp[anc][j - 1][0][1], dp[i][j - 1][0][1] + dp[anc][j - 1][1][1]); dp[i][j][1][0] = min(dp[i][j - 1][1][0] + dp[anc][j - 1][0][0], dp[i][j - 1][1][1] + dp[anc][j - 1][1][0]); dp[i][j][1][1] = min(dp[i][j - 1][1][0] + dp[anc][j - 1][0][1], dp[i][j - 1][1][1] + dp[anc][j - 1][1][1]); } } return; } LL solve(int u, bool opt1, int v, bool opt2) { if(dep[u] > dep[v]) swap(u, v), swap(opt1, opt2); LL flca[2], fu[2] = {INF, INF}, fv[2] = {INF, INF}, new_fu[2] = {INF, INF}, new_fv[2] = {INF, INF}; fu[opt1] = dp1[u][opt1], fv[opt2] = dp1[v][opt2]; ROF(i, t, 0) { if(dep[F[v][i]] >= dep[u]) { new_fv[0] = new_fv[1] = INF; FOR(x, 0, 1) FOR(y, 0, 1) new_fv[x] = min(new_fv[x], fv[y] + dp[v][i][y][x]); FOR(x, 0, 1) fv[x] = new_fv[x]; v = F[v][i]; } } if(u == v) return fv[opt1] + dp2[u][opt1]; ROF(i, t, 0) { if(F[u][i] != F[v][i]) { new_fu[0] = new_fu[1] = new_fv[0] = new_fv[1] = INF; FOR(x, 0, 1) FOR(y, 0, 1) new_fv[x] = min(new_fv[x], fv[y] + dp[v][i][y][x]), new_fu[x] = min(new_fu[x], fu[y] + dp[u][i][y][x]); FOR(x, 0, 1) fu[x] = new_fu[x], fv[x] = new_fv[x]; u = F[u][i], v = F[v][i]; } } int lca = F[u][0]; flca[0] = dp2[lca][0] + dp1[lca][0] - dp1[u][1] - dp1[v][1] + fu[1] + fv[1]; flca[1] = dp2[lca][1] + dp1[lca][1] - min(dp1[u][0], dp1[u][1]) - min(dp1[v][0], dp1[v][1]) + min(fu[0], fu[1]) + min(fv[0], fv[1]); return min(flca[0], flca[1]); } signed main() { read(n), read(m); char type[10]; cin >> type; t = log(n) / log(2) + 1; FOR(i, 1, n) read(p[i]), G[i].clear(); int a, x, b, y; FOR(i, 2, n) { read(x), read(y); G[x].push_back(y), G[y].push_back(x); table[MP(x, y)] = table[MP(y, x)] = true; } BFS(), dp_prework(); FOR(i, 1, m) { read(a), read(x), read(b), read(y); if(!x && !y && table.find(MP(a, b)) != table.end()) puts("-1"); else printf("%lld\n", solve(a, x, b, y)); } return 0; }