P4149 點分治 + 桶

題目傳送門

題意:

給一棵 \dpi{150}n 個點的樹,每條邊有權。求一條簡單路徑,權值和等於 \dpi{150}k ,且邊的數量最小。輸出最小邊的數量。

數據範圍:\dpi{150} 1 \leqslant n \leqslant 2 \cdot 10^5 , k \leqslant 10^6 。

題解:

因爲 \dpi{150} k 比較小,考慮開一個桶。我這個做法把數組改成 \dpi{150} map 就超時了。

桶的含義:當前子樹中,\dpi{150} bac[d] 表示深度爲 \dpi{150}d 的路徑的最小邊數。

\dpi{150} dis1[i] 表示當前子樹中,第 \dpi{150}i 個點的深度。

\dpi{150} dis2[i] 表示當前子樹中,根節點到第 \dpi{150}i 個點的邊數。

所以答案是 \dpi{150} ans = min(ans , bac[k-dis1[i]] + dis2[i]) 。

感受:

這道題用了很笨的做法寫了很久,看了答案,發現想複雜了,然後用更簡便的做法又做了一遍。

桶真的好用。

代碼:

#include<bits/stdc++.h>
using namespace std ;
typedef long long ll ;
const int maxn = 2e5 + 5 ;
const int maxm = 1e6 + 5 ;
int n , k ;
int num , head[maxn] ;
int root ;
int siz[maxn] ;
int vis[maxn] ;
int maxp[maxn] ;
int sum ;
int cur = 0 ;
int dis1[maxn] , dis2[maxn] ;
int bac[maxm] ;
struct Edge
{
	int u , v , next , w ;
} edge[maxn << 1] ;
void add_edge(int u , int v , int w)
{
	edge[num].v = v ;
	edge[num].w = w ;
	edge[num].next = head[u] ;
	head[u] = num ++ ;
}
void get_root(int fa , int u)
{
    siz[u] = 1 , maxp[u] = 0 ;
    for(int i = head[u] ; i != -1 ; i = edge[i].next)
    {
        int v = edge[i].v ;
        if(v == fa || vis[v])  continue ;
        get_root(u , v) ;
        siz[u] += siz[v] ;
        maxp[u] = max(maxp[u] , siz[v]) ;
    }
    maxp[u] = max(maxp[u] , sum - siz[u]) ;
    if(maxp[u] < maxp[root]) root = u ; 
}
void get_dis(int fa , int u , int d , int cnt)
{
	cur ++ ;
	dis1[cur] = d , dis2[cur] = cnt ;
	for(int i = head[u] ; i != -1 ; i = edge[i].next)
	{
		int v = edge[i].v , w = edge[i].w ;
		if(v == fa || vis[v])  continue ;
		get_dis(u , v , d + w , cnt + 1) ; //獲得d數組
	}	
}
int cal(int p , int d , int cnt)
{
	int ans = 1e9 ;
	int last = 0 ;
	cur = 0 ;
	bac[0] = 0 , dis1[0] = 0 , dis2[0] = cnt ;
	for(int i = head[p] ; i != -1 ; i = edge[i].next)
	{
		int v = edge[i].v , w = edge[i].w ;
		if(vis[v])  continue ;
		get_dis(p , v , d + w , cnt + 1) ;
		for(int i = last + 1 ; i <= cur ; i ++)
		  if(dis1[i] <= k)
		    ans = min(ans , bac[k - dis1[i]] + dis2[i]) ;
		for(int i = last + 1 ; i <= cur ; i ++)
		  if(dis1[i] <= k)
		    bac[dis1[i]] = min(bac[dis1[i]] , dis2[i]) ;
		last = cur ;
	}	
	for(int i = 0 ; i <= cur ; i ++)
	  if(dis1[i] <= k)
	    bac[dis1[i]] = 1e9 ;
	return ans ;
}
int solve(int p)
{	
    int ans = 1e9 ;
	ans = min(ans , cal(p , 0 , 0)) ; //p子樹 
	vis[p] = 1 ; //刪除p節點
	for(int i = head[p] ; i != -1 ; i = edge[i].next)
	{
	   int v = edge[i].v , w = edge[i].w ;
	   if(vis[v])  continue ;
	   root = 0 , maxp[0] = 1e9 , sum = siz[v] ; //初始化 
	   get_root(0 , v) ;
	   ans = min(ans , solve(root)) ;	
	}
	return ans ; 
}
int main()
{
    scanf("%d%d" , &n , &k) ;
    num = 0 , memset(head , -1 , sizeof(head)) ;
    memset(vis , 0 , sizeof(vis)) ;
    for(int i = 1 ; i <= n - 1 ; i ++)
    {
    	int u , v , w ;
    	scanf("%d%d%d" , &u , &v , &w) ;
    	u ++ , v ++ ;
		add_edge(u , v , w) , add_edge(v , u , w) ;
	}
	for(int i = 0 ; i <= 1e6 ; i ++)  bac[i] = 1e9 ;
	root = 0 , maxp[0] = 1e9 ; //初始化 
	sum = n ; 
	get_root(0 , 1) ; //最開始得到整棵樹的root
	int x = solve(root) ;
	if(x == 1e9)  printf("-1\n") ;
	else  printf("%d\n" , x) ;
    return 0 ;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章