ポイント分割統治アルゴリズムは、ツリーのパス統計に非常に効果的なアルゴリズムです。ツリー上のパスなどの問題を解決するように求められると、点分割および征服でそれを解決できるかどうかを考えることができます。
分割統治は、問題をいくつかのサブ問題に分解する再帰的な問題解決方法であり、ツリーの分割統治はツリーの形状に影響されます。
ルートノードを不適切に選択すると、ツリーの深さが大きくなりすぎ、時間の複雑性も非常に高くなるため、最大のサブツリーを最小化するために適切なポイントを選択する必要があります。この条件を満たす点は木の重心と呼ばれ、dfsによって取得できます。
int get_root(int u,int fa)//找出树的重心
{
//siz[i]是指以i为根子树的大小,maxson[i]是指以i为根最大的子树的大小
//SIZE 为当前处理的这棵树的大小 maxx代表已经找到的最大子树的最小值
siz[u] = 1;maxson[u] = 0;
for(int i = head[u];i != 0;i = edge[i].next){
int v = edge[i].to;
if(vis[v] || v == fa) continue;
get_root(v,u);
siz[u] = siz[u] + siz[v];
maxson[u] = max(maxson[u],siz[v]);
}
maxson[u] = max(maxson[u],SIZE-siz[u]);
if(maxson[u] < maxx) root = u,maxx = maxson[u];
}
同様に、現在のノードをルートノードとするサブツリーの各ノードのルートノードへの距離値も取得する必要があります。
void get_dis(int u,int fa,int d)//从每一棵新建的子树求距离函数
{
dis[++num] = d;//d数组保存当前根节点到每一个点的距离
for(int i = head[u];i;i = edge[i].next){
int v = edge[i].to;
if(vis[v] || v == fa) continue;
get_dis(v,u,d+edge[i].w);
}
return ;
}
次はコア機能です。このトピックでは、最初にルートノードを直接カウントすると統計が重複するため、次の統計ではこれらの重複部分を排除する必要があります。したがって、ソルブ関数lenの値は変化します。ダブルポインターメソッドを使用してdis配列を並べ替えると、この条件を満たすパスのペアの数がわかります。
分割統治関数では、サブツリーのルートノードから開始するため、最初の解のlenが異なるため、ルートツリーからサブツリーのルートノードまでの距離を追加して、答えを数える必要があります。
int calculate(int rt,int len)
{
num = 0;
memset(dis,0,sizeof(dis));
get_dis(rt,0,len);//以当前子树的根节点出发 获得一下路径长度
sort(dis+1,dis+1+num);
int L = 1,R = num,res = 0;
while(L <= R){
//双指针法 确定答案
if(dis[L] + dis[R] <= k){
res += R-L;+
L++;
}
else R--;
}
return res;
}
void Divide(int rt)//分治函数 核心部分
{
ans = ans + calculate(rt,0);
vis[rt] = 1;
for(int i = head[rt];i;i = edge[i].next){
//对没棵子树进行分治
int v = edge[i].to;
if(vis[v]) continue;
ans = ans - calculate(v,edge[i].w);
SIZE = siz[v];//都重新换一下 以下的信息全部重新赋一个值
maxx = inf;
root = 0;
get_root(v,rt);
Divide(root);//分治新的根节点
}
return ;
}
一般的に言って、点分割統治問題の3つの関数の具体的な内容は、重心を見つけ、距離を計算し、解を分割統治することです。重要なのは解決関数(計算関数)です。これは問題によって異なり、特定の分析が必要です。
完全なコード:
Problem: 1741 User: tzteyang777
Memory: 1316K Time: 844MS
Language: G++ Result: Accepted
Source Code
//#include <bits/stdc++.h>
#include <cstring>
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstdio>
using namespace std;
typedef long long ll;
//点分治
//找到一个节点使其最大子树的大小尽量小 这时分治递归的时间复杂度是最低的
//而这样的节点 就叫做树的重心 可以用一个dfs来求O(n)的时间
const int MAXN = 1e4+7;
#define inf 0x3f3f3f3f
int head[MAXN],maxson[MAXN],vis[MAXN],siz[MAXN],dis[MAXN];
int cnt,SIZE,maxx,num,k,root;
ll ans;
struct Edge
{
int to,next,w;
}edge[MAXN<<1];
void addedge(int u,int v,int w)
{
edge[++cnt].to = v;
edge[cnt].w = w;
edge[cnt].next = head[u];
head[u] = cnt;
}
int get_root(int u,int fa)//找出树的重心
{
//siz[i]是指以i为根子树的大小,maxson[i]是指以i为根最大的子树的大小
//SIZE 为当前处理的这棵树的大小 maxx代表已经找到的最大子树的最小值
siz[u] = 1;maxson[u] = 0;
for(int i = head[u];i != 0;i = edge[i].next){
int v = edge[i].to;
if(vis[v] || v == fa) continue;
get_root(v,u);
siz[u] = siz[u] + siz[v];
maxson[u] = max(maxson[u],siz[v]);
}
maxson[u] = max(maxson[u],SIZE-siz[u]);
if(maxson[u] < maxx) root = u,maxx = maxson[u];
}
void get_dis(int u,int fa,int d)//从每一棵新建的子树求距离函数
{
dis[++num] = d;//d数组保存当前根节点到每一个点的距离
for(int i = head[u];i;i = edge[i].next){
int v = edge[i].to;
if(vis[v] || v == fa) continue;
get_dis(v,u,d+edge[i].w);
}
return ;
}
int calculate(int rt,int len)
{
num = 0;
memset(dis,0,sizeof(dis));
get_dis(rt,0,len);
sort(dis+1,dis+1+num);
int L = 1,R = num,res = 0;
while(L <= R){
//双指针法 确定答案
if(dis[L] + dis[R] <= k){
res += R-L;+
L++;
}
else R--;
}
return res;
}
void Divide(int rt)//分治函数 核心部分
{
ans = ans + calculate(rt,0);
vis[rt] = 1;
for(int i = head[rt];i;i = edge[i].next){
//对没棵子树进行分治
int v = edge[i].to;
if(vis[v]) continue;
ans = ans - calculate(v,edge[i].w);
SIZE = siz[v];//都重新换一下
maxx = inf;
root = 0;
get_root(v,rt);
Divide(root);//分治新的根节点
}
return ;
}
int main()
{
int n;
while(~scanf("%d%d",&n,&k)&&(n&&k)){
cnt = 0;
memset(head,0,sizeof(head));
for(int i = 1;i < n;i ++){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
addedge(a,b,c);
addedge(b,a,c);
}
ans = 0;
memset(vis,0,sizeof(vis));
maxx = inf;SIZE = n;
get_root(1,0);
Divide(root);
printf("%lld\n",ans);
}
return 0;
}