[SPOJ - FTOUR2] Free tour II(点分治 + 背包dp + 启发式合并)
SPOJ - FTOUR2 Free tour II
problem
給定一棵樹,以及 mmm 個擁擠城市編號,選擇一條最多包含 kkk 個擁擠城市的簡單路徑。
每條邊有一個有趣度 www,可正可負。簡單路徑的價值定義為包含邊的有趣度之和。
求最大價值。n≤2e5,∣w∣≤1e4n\le 2e5,|w|\le 1e4n≤2e5,∣w∣≤1e4。
solution
取點作根,計算過根的符合條件的簡單路徑價值,如果不過根就轉化成各個獨立子樹的子問題。
選取重心作根,點分治。
問題仍然在于怎么計算過根的符合條件的簡單路徑價值。
暴力的樹形 dpdpdp,dpi,j:idp_{i,j}:idpi,j?:i 子樹內擁擠城市為 jjj 的最大路徑價值。
每次枚舉 iii 的兒子,每次做樹上背包,之后再合并,時間復雜度是平方級別的。
dpu,x+dpv,y(x+y≤k)→ansdp_{u,x}+dp_{v,y}(x+y\le k)\rightarrow ansdpu,x?+dpv,y?(x+y≤k)→ans
dpu,x=max?(dpu,x,dpv,x)dp_{u,x}=\max(dp_{u,x},dp_{v,x})dpu,x?=max(dpu,x?,dpv,x?)
考慮優化。
對于第 iii 個兒子而言,如果第 iii 個兒子使用的擁擠城市數量為 jjj,那么前 i?1i-1i?1 個兒子的城市擁擠數量可以使用 1~k?j1\sim k-j1~k?j。
所以可以“前綴和”優化,這里去前綴最大值
dpu,x:dp_{u,x}:dpu,x?: 前 i?1i-1i?1 個兒子城市擁擠數量使用不超過 xxx 的最大價值。
這樣就可以線性枚舉 jjj 計算貢獻。
最后就是在枚舉前 i?1i-1i?1 個兒子的擁擠數量問題上。
比如第 ppp 個兒子內部最多可以找到一條路徑有 kkk 個擁擠城市,而現在的兒子最多只能有 y(y<k)y(y<k)y(y<k) 個。
但是從第 ppp 個兒子開始就必須枚舉完使用 kkk 個擁擠城市,才能將前面兒子的信息覆蓋完全。
但是這樣的時間復雜度就會飛起。
如果交換現在的兒子和第 ppp 個兒子順序,那么只用枚舉完 yyy 個擁擠城市就已經覆蓋了前面兒子的所有路徑使用情況。
所以將兒子按照內部最多能找到一條路徑有 ddd 個擁擠城市,ddd 升序排序。
相當于只將每個點枚舉了一次,這就是啟發式合并。
code
#include <bits/stdc++.h> using namespace std; #define maxn 200005 #define inf 0x7f7f7f7f vector < pair < int, int > > G[maxn]; int n, m, k, Max, root, N, ans; bool crowd[maxn], vis[maxn]; int siz[maxn], f[maxn], g[maxn]; struct node { int d, v, w; }MS[maxn];void dfs( int u, int fa ) {int maxsiz = 0; siz[u] = 1;for( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i].first;if( vis[v] or v == fa ) continue;else dfs( v, u ), siz[u] += siz[v];maxsiz = max( maxsiz, siz[v] );}maxsiz = max( maxsiz, N - siz[u] );if( maxsiz < Max ) Max = maxsiz, root = u; }int dfs( int u, int fa, int cnt ) {if( cnt == k ) return cnt;int ret = cnt;for( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i].first;if( vis[v] or v == fa ) continue;ret = max( ret, dfs( v, u, cnt + crowd[v] ) );}return ret; }void dfs( int u, int fa, int val, int cnt ) {if( cnt > k ) return;g[cnt] = max( g[cnt], val );for( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i].first, w = G[u][i].second;if( vis[v] or v == fa ) continue;dfs( v, u, val + w, cnt + crowd[v] );} }void calc( int u ) {if( crowd[u] ) k --;int cnt = 0;for( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i].first, w = G[u][i].second;if( vis[v] ) continue;MS[++ cnt] = { dfs( v, u, crowd[v] ), v, w };}//計算出v子樹內一條路最多能有多少個擁擠城市sort( MS + 1, MS + cnt + 1, []( node x, node y ) { return x.d < y.d; } );//啟發式合并for( int i = 1;i <= cnt;i ++ ) {int v = MS[i].v, w = MS[i].w;dfs( v, u, w, crowd[v] ); //計算出v子樹內訪問x個擁擠城市的最大有趣度/*f[j]:前i-1個子樹信息總和 訪問j個擁擠城市的最大值g[j]:只針對第i個子樹 訪問j個擁擠城市的最大值 每次都會在dfn(v,u,w,crowd[v])重新計算顯然 g[j]+f[x](0<=x<=k-j) 都可以對最終答案進行貢獻這里對f[x]進行前綴max 有jx平方的時間變成j線性實際上x<=MS[i-1].d 要注意這個限制 不然可能會莫須有地更新到不存在的擁擠城市個數 影響f然后將i子樹信息合并到i-1子樹信息內 即g->f轉到下一個子樹i+1*/if( i ^ 1 ) {for( int j = 1;j <= MS[i - 1].d;j ++ ) f[j] = max( f[j], f[j - 1] );for( int j = 0;j <= MS[i].d;j ++ ) ans = max( ans, f[min( MS[i - 1].d, k - j )] + g[j] );}for( int j = 0;j <= MS[i].d;j ++ ) f[j] = max( f[j], g[j] ), g[j] = 0;}for( int i = 0;i <= MS[cnt].d;i ++ ) {ans = max( ans, f[i] );f[i] = g[i] = 0;}if( crowd[u] ) k ++; }void dfs( int u ) {vis[u] = 1;calc( u );for( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i].first;if( vis[v] ) continue;Max = inf, N = siz[v];dfs( v, u );dfs( root );} }int main() {scanf( "%d %d %d", &n, &k, &m );for( int i = 1, x;i <= m;i ++ ) scanf( "%d", &x ), crowd[x] = 1;for( int i = 1, u, v, w;i < n;i ++ ) {scanf( "%d %d %d", &u, &v, &w );G[u].push_back( { v, w } );G[v].push_back( { u, w } );}Max = inf, N = n;dfs( 1, 0 );dfs( root );printf( "%d\n", ans );return 0; }總結
以上是生活随笔為你收集整理的[SPOJ - FTOUR2] Free tour II(点分治 + 背包dp + 启发式合并)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 肥西房产备案(肥西置地备案)
- 下一篇: 四川省重点大学有那几所