点分治(树)

点分治,是处理树上路径的一个极好的工具
重心:找到一个点,其所有的子树中最大的子树的节点数最少,那么这个点就是这棵树的重心,

1存储结构:伪链表。

struct node
{
	int to,next,len;
}t[2*maxn+5];

void addedge(int u,int v,int l)
{
  cnt ++;
  t[cnt].to = v;
  t[cnt].next = head[u];
  t[cnt].len = l;
  head[u] = cnt;
  return;
}

2递归找重心:

void getroot(int u,int fa) //重心节点保存在全局变量root中。
{
  sim[u] = 1; mxson[u] = 0;
  for(int i = head[u];i;i = t[i].next)
    {
      int v = t[i].to;
      if(v == fa || vis[v])continue;
      getroot(v,u);
      sim[u] = sim[u] + sim[v];
      mxson[u] = max(sim[v],mxson[u]);
    }
  mxson[u] = max(mxson[u],S-sim[u]);
  if(mxson[u]<MX){root = u;MX = mxson[u];}
}

3找所有点到根节点的距离

void getdis(int u,int fa,int dist)  //距离保=保存在全局变量dis[]中。
{
  dis[++summar] = dist;
  for(int i = head[u];i;i=t[i].next)
    {
      int v = t[i].to;
      if(vis[v]||v == fa)continue;
      getdis(v,u,dist+t[i].len);
    }
  return;
}

4计算结果(排除多余的重复结果,容斥原理)

void Divide(int tr)       //tr是根节点,ans是答案,consolate()计算--,依据情况而定,
{
  ans = ans + consolate(tr,0);
  vis[tr] = true;
  for(int i = head[tr];i;i = t[i].next)
    {
      int v = t[i].to;
      if(vis[v])continue;
      ans = ans - consolate(v,t[i].len);
      S = sim[v]; root = 0;
      MX = INF; getroot(v,0);
      Divide(root);
    }
  return;
}

例题1】【poj1741】tree 边权值小于等于K的路径数
给一颗n个节点的树,每条边上有一个距离v。定义d(u,v)为u到v的最小距离。给定k值,求有多少点对(u,v)使u到v的距离小于等于k。
分析
在consolate函数,计算点到根的距离,然后通过根节点就可以把所有的点连接起来。
在这里插入图片描述
上面图A为根节点,C-A距离加上A-E距离就可以得出C-E的距离,C-A距离加上A-D的距离就可以得出C-D距离,显然这种情况会出现错误,但是在容斥里面会排除,不用管,
那么将每一个节点到根的距离排序,从距离最小的节点枚举(这个点为起点),

while(L<=R)
    {
      if(dis[R] + dis[L]<=k){tep = tep + R - L; L++;}
      else R--;
    }
    //L 为起始点,如果加上距离最大R的值小于K,那么L-R的点都满足距离小于K,那么就有R-L条路经满足。

完整代码:

#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#define maxn 10002
#define INF 2147483646
#define ll long long
using namespace std;

inline int gi()
{
  int date = 0,m = 1; char ch = 0;
  while(ch!='-'&&(ch<'0'||ch>'9'))ch = getchar();
  if(ch=='-'){m = -1; ch = getchar();}
  while(ch>='0' && ch<='9')
    {
      date = date*10+ch-'0';
      ch = getchar();
    }return date*m;
}
inline void write(ll qw)
{
  if(qw<0){putchar('-'); qw = -qw;}
  if(qw>9)write(qw/10);
  putchar(qw%10+'0');
}

//权值小于等下K 

struct node
{
	int to,next,len;
}t[2*maxn+5];

int head[maxn+5];

//head[]--t构成伪链表来存储树; 
int sim[maxn+5],mxson[maxn+5];  // sim[i]---表示i为节点的子树的个数,mxson[i]---表示i为节点的根中最大子树。
 
bool vis[maxn+5];  //vis【i】--在容斥中用到,判断i之前是否用过。 
int MX,root,dis[maxn+5],summar;      //dis[i]---表示节点i到根节点的距离。 
ll ans;
int n,k,cnt,S;

void addedge(int u,int v,int l)  //u,v是顶点,l是权值。 
{
  cnt ++;
  t[cnt].to = v;
  t[cnt].next = head[u];
  t[cnt].len = l;
  head[u] = cnt;
  return;
}

void getroot(int u,int fa) 
{
  sim[u] = 1; mxson[u] = 0;
  for(int i = head[u];i;i = t[i].next)
    {
      int v = t[i].to;
      if(v == fa || vis[v])continue;
      getroot(v,u);
      sim[u] = sim[u] + sim[v];
      mxson[u] = max(sim[v],mxson[u]);
    }
  mxson[u] = max(mxson[u],S-sim[u]);
  if(mxson[u]<MX){root = u;MX = mxson[u];}
}

void getdis(int u,int fa,int dist)     //得到所有点到根的距离,u是根,fa是父节点, dist是距离, 
{
  dis[++summar] = dist;
  for(int i = head[u];i;i=t[i].next)
    {
      int v = t[i].to;
      if(vis[v]||v == fa)continue;
      getdis(v,u,dist+t[i].len);
    }
  return;
}

int consolate(int sta,int len1)
{
  summar = 0;
  memset(dis,0,sizeof(dis));
  getdis(sta,0,len1);
  sort(dis+1,dis+summar+1);
  int L = 1,R = summar,tep=0;
  while(L<=R)
    {
      if(dis[R] + dis[L]<=k){tep = tep + R - L; L++;}
      else R--;
    }
  return tep;
}

void Divide(int tr)
{
  ans = ans + consolate(tr,0);
  vis[tr] = true;
  for(int i = head[tr];i;i = t[i].next)
    {
      int v = t[i].to;
      if(vis[v])continue;
      ans = ans - consolate(v,t[i].len);
      S = sim[v]; root = 0;
      MX = INF; getroot(v,0);
      Divide(root);
    }
  return;
}

int main()
{
  freopen("Tree.in","r",stdin);
  int u,v,l;
  while(1)
    {
      n = gi(); k = gi();
      if(n == 0 && k == 0)break;
      cnt = 0; memset(head,0,sizeof(head));
      for(int i = 1; i <= n - 1; i ++)
    {
      u = gi(); v = gi(); l = gi();
      addedge(u,v,l); addedge(v,u,l);
    }
      ans = 0;
      memset(vis,0,sizeof(vis));
      MX = INF ; S = n; getroot(1,0);
      Divide(root);
      write(ans);putchar('\n');
    }
  return 0;
}

【例题3】【luogu P3806】点分治1 询问边权值为K的路径是否存在

给定一棵树,询问长度为k的树上路径是否存在。
对于每个k每行输出一个答案,存在输出“AYE”,否则输出”NAY”。(k<=10000000)

分析

注意到k的大小在数组可开范围之内,开一个数组sum[i]记录长度为i的路径条数。
点分治计算所有路径长度时注意容斥原理去重,然后在线直接O(1)回答询问即可。

#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#define maxn 100100
#define INF 2147483646
#define ll long long
using namespace std;

inline int gi()
{
  int date = 0,m = 1; char ch = 0;
  while(ch!='-'&&(ch<'0'||ch>'9'))ch = getchar();
  if(ch=='-'){m = -1; ch = getchar();}
  while(ch>='0' && ch<='9')
    {
      date = date*10+ch-'0';
      ch = getchar();
    }return date*m;
}

//权值等于k 

int sum[10000010];
struct node
{
	int to,next,len;
}t[maxn+5];

int head[maxn+5],cnt;
int sim[maxn+5],mxson[maxn+5],dis[maxn+5];
int n,m,MX,root,sumary,Smer;
bool vis[maxn+5];

void addedge(int s,int f,int l)
{
  cnt ++;
  t[cnt].to = f;
  t[cnt].next = head[s];
  t[cnt].len = l;
  head[s] = cnt;
  return;
}

void getroot(int u,int fa)
{
  sim[u] = 1; mxson[u] = 0;
  for(int i = head[u];i;i = t[i].next)
    {
      int v = t[i].to;
      if(vis[v]||v == fa)continue;
      getroot(v,u);
      sim[u] = sim[u] + sim[v];
      mxson[u] = max(mxson[u],sim[v]);
    }
  mxson[u] = max(mxson[u],Smer - sim[u]);
  if(mxson[u]<MX){root = u; MX = mxson[u];}
}

void getdis(int u,int fa,int dist)
{
  dis[++sumary] = dist;
  for(int  i = head[u];i;i = t[i].next)
    {
      int v = t[i].to;
      if(vis[v]||v == fa)continue;
      getdis(v,u,lon+t[i].len);
    }
  return;
}

void consolate(int st,int prim,int jud)
{
  sumary = 0;
  getdis(st,0,prim);
  if(jud == 1)
    {
      for(int i  = 1;i <= sumary-1; i ++)
        for(int j = i+1;j <= sumary; j ++)
      sum[dis[i]+dis[j]]++;
    }
  else if(jud == 0)
    {
      for(int i = 1; i <= sumary-1; i ++)
    for(int j = i+1; j <= sumary; j ++)
      sum[dis[i]+dis[j]]--;
    }return;
}

void divide(int tr)
{
  vis[tr] = true;
  consolate(tr,0,1);
  for(int i = head[tr];i;i = t[i].next)
    {
      int v = t[i].to;
      if(vis[v])continue;
      consolate(v,t[i].len,0);
      MX = INF; root = 0; Smer = sim[v];
      getroot(v,0);
      divide(root);
    }
  return;
}

void give_ans()
{
  for(int i = 1; i <= m ; i ++)
    {
      int k = gi();
      if(sum[k])printf("AYE\n");
      else printf("NAY\n");
    }
  return;
}

int main()
{
  freopen("divide.in","r",stdin);
  n = gi(); m = gi(); cnt = 0;
  for(int i = 1; i <= n-1 ; i++)
    {
      int x = gi(),y = gi(),l = gi();
      addedge(x,y,l); addedge(y,x,l);
    }
  root = 0; MX = INF; Smer = n;
  getroot(1,0);
  divide(root);
  give_ans();
  return 0;
}

【luogu P2634】聪聪可可
输入一个树, 问有多少对点之间的距离模3为0, 并按照题目所给方式输出

分析:
利用点分治在计算所有路径长度,把路径长度对3取模,用t[0],t[1],t[2]分别记录模为0、1、2的情况,那么显然答案就是t[1]*t[2]*2+t[0]*t[0]。

#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#define maxn 20002
#define INF 2147483646
#define mod 3
#define ll long long
using namespace std;

inline int gi()
{
  int date = 0,m = 1; char ch = 0;
  while(ch!='-'&&(ch<'0'||ch>'9'))ch = getchar();
  if(ch=='-'){m = -1; ch = getchar();}
  while(ch>='0' && ch<='9')
    {
      date = date*10+ch-'0';
      ch = getchar();
    }return date*m;
}
inline void write(ll qw)
{
  if(qw<0){putchar('-'); qw = -qw;}
  if(qw>9)write(qw/10);
  putchar(qw%10+'0');
}

struct node{int to,next,len;}t[2*maxn+5];
int head[maxn+5],cnt;
int sim[maxn+5],mxson[maxn+5],sum[4];
ll ans;
int Sumer,MX,dec1,root,n;
bool vis[maxn+5];

void addedge(int u,int v,int l)
{
  cnt ++;
  t[cnt].to = v;
  t[cnt].next = head[u];
  t[cnt].len = l;
  head[u] = cnt;
  return;
}

void getroot(int u,int fa)
{
  sim[u] = 1;mxson[u] = 0;
  for(int i = head[u];i;i = t[i].next)
    {
      int v= t[i].to;
      if(v == fa || vis[v])continue;
      getroot(v,u);
      sim[u] = sim[u] + sim[v];
      mxson[u] = max(mxson[u],sim[v]);
    }
  mxson[u] = max(mxson[u],Sumer - sim[u]);
  if(mxson[u]<MX){MX = mxson[u]; root = u;}
}

void getdis(int u,int fa,int lon)
{
  sum[lon%mod]++;
  for(int i = head[u];i;i = t[i].next)
    {
      int v = t[i].to;
      if(vis[v]||v == fa)continue;
      getdis(v,u,(lon+t[i].len)%mod);
    }return;
}

ll consolate(int st,int prim_len)
{
  sum[0] = sum[1] = sum[2] = 0;
  getdis(st,0,prim_len);
  ll tep = 2*sum[1]*sum[2] + sum[0]*(sum[0]-1) + sum[0];
  return tep;
}

void Divide(int temproot)
{
  ans = ans + consolate(temproot,0);
  vis[temproot] = true;
  for(int i = head[temproot];i;i = t[i].next)
    {
      int v = t[i].to;
      if(vis[v])continue;
      ans = ans - consolate(v,t[i].len);
      MX = INF; root = 0; Sumer = sim[v];
      getroot(v,0);
      Divide(root);
    }
  return;
}

void gcd(ll a,ll b)
{
  if(b == 0)dec1 = a;
  else gcd(b,a%b);
}
int main()
{
  freopen("game.in","r",stdin);
  n = gi();
  for(int i = 1; i <= n-1 ; i ++)
    {
      int u = gi(),v= gi(),l = gi()%mod;
      addedge(u,v,l);addedge(v,u,l);
    }
  MX = INF; root = 0;
  Sumer = n; ans = 0;
  getroot(1,0);
  Divide(root);
  ll tep1 = n*n; gcd(ans,tep1);
  write(ans/dec1); putchar('/'); write(tep1/dec1);
  return 0;
}

Description 固定边权的路径最小数
给一棵树n个点,n-1条边,每条边有权.求一条简单路径,权值和等于K,且边的数量最小.N <= 200000, K <= 1000000;
分析:
增设一个数组,不光要记录路径的权值和,还要记录路径的长度 ,
需要加一个数组num[i],表示长度为i且权值和为m的路径条数,

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int N=200010;
int f[N],sz[N],tt;
int n,k,root,sum,tot=0,head[N],num[N];
//num[i]--表示长度为i且权值和为k的路径条数 
struct node{
    int x,y,v,nxt;
};
node t[N<<1];  //t和head组成了伪链表。 
bool vis[N];    //vis【i】--在容斥中用到,判断i之前是否用过。 



struct po{
    int len,v;      
    
};
po line[N<<1],d[N];//d在一个连通块内在到达根节点的路径权值和以及边数 

void add(int u,int w,int z)
{
    tot++;
    t[tot].x=u;t[tot].y=w;t[tot].v=z;t[tot].nxt=head[u];head[u]=tot;
}

int cmp(const po &a,const po &b)
{
    return a.v<b.v||a.v==b.v&&a.len<b.len;
}

void getroot(int now,int fa)
{
    f[now]=0;
    sz[now]=1;
    for (int i=head[now];i;i=t[i].nxt)
        if (t[i].y!=fa&&!vis[t[i].y])
        {
            getroot(t[i].y,now);
            sz[now]+=sz[t[i].y];
            f[now]=max(f[now],sz[t[i].y]);
        }
    f[now]=max(f[now],sum-sz[now]);
    if (f[now]<f[root]) root=now;
}

void getdeep(int now,int fa)
{
    d[++tt]=line[now];
    for (int i=head[now];i;i=t[i].nxt)
        if (t[i].y!=fa&&!vis[t[i].y])
        {
            line[t[i].y].v=line[now].v+t[i].v;
            line[t[i].y].len=line[now].len+1;
            getdeep(t[i].y,now);
        }
}

int cal(int now,int z)
{
    tt=0;
    getdeep(now,0);
    sort(d+1,d+1+tt,cmp);
    int l=1,r=tt;
    while (l<r)
    {
        while (d[l].v+d[r].v>k&&r>l) r--;
        int rr=r;
        if (r<=l) break;
        while (d[l].v+d[r].v==k)
        {
            num[d[l].len+d[r].len]+=z;
            r--;
            if (r<=l) break;
        }
        r=rr;
        l++;
    }
}

void divide(int now)
{
    line[now].len=0; line[now].v=0;
    vis[now]=1;
    cal(now,1);
    for (int i=head[now];i;i=t[i].nxt)
        if (!vis[t[i].y])
        {
            line[now].len=1; line[now].v=t[i].v;
            cal(t[i].y,-1);     //一定要有去重操作 
            root=0;
            sum=sz[t[i].y];
            getroot(t[i].y,root);
            divide(root);
        }
}

int main()
{
    scanf("%d%d",&n,&k);
    for (int i=1;i<n;i++)
    {
        int u,w,z;
        scanf("%d%d%d",&u,&w,&z);
        u++; w++;
        add(u,w,z); add(w,u,z);
    }

    f[0]=1000000000; sum=n; root=0;
    getroot(1,0);
    divide(root);

    for (int i=0;i<=n;i++)
        if (num[i]>0) {
            printf("%d",i);
            return 0;
        }
    printf("-1");
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值