点分治专题——bzoj 1468 &bzoj 2152 题解

【前言】最近一直在忙着学算法,但是效果似乎不是很好。前段时间的树剖也快忘了= =。树套树没熟练,就开始写主席树了= =。更别说本身就不是很懂的莫比乌斯反演了。~~决定好好复习一下。

【点分治的作用】套用SYC大神的话说是:用来解决树上路径点权统计问题。

【大致流程】

找出这颗树的重心。

统计经过这个重心的答案

用重心把树割开

④对每个“小树”做同样的事

【Q1——重心】其实找重心再进行计算只是为了不被卡链。什么是重心?就是当前树中的一个点K,使得MAX(SON[K])最小。SON[K]指的是以K为根的情况下某个孩子的点数。感性的想,重心在树的中间位置。

【Q2——统计答案】假设我们已经确定了一个重心K。我们先计算和K有关的(即经过K的路径或是以K为起点/终点的路径)答案个数,然后再递归每一棵小树,同样进行找重心、计算的过程。而且有些时候,我们计算出有关K的答案是有重复的,因此我们在递归的时候还要减去重复的(容斥原理)。

【Q3——边界】怎么使得某棵小树和其他树分开呢?其实很简单,我们可以开个1..n的布尔数组,表示x是否成为过重心。如果成为过,我在搜索的时候就可以直接退出了。

【T1——BZOJ】

1468: Tree

Time Limit: 10 Sec   Memory Limit: 64 MB
Submit: 306   Solved: 139

Description

给你一棵TREE,以及这棵树上边的距离.问有多少对点它们两者间的距离小于等于K

Input

N(n<=40000) 接下来n-1行边描述管道,按照题目中写的输入 接下来是k

Output

一行,有多少对点之间的距离小于等于k

Sample Input

7
1 6 13
6 3 9
3 5 7
4 1 3
2 4 20
4 7 2
10

Sample Output

5


【分析】真是一道经典题,POJ,USACO上都有。我们每次找到重心K后,把每个点到K的相对距离都算出来。然后把他们存到一个数组里去,用O(NLOGN)的快排和O(N)的扫描算出点对数。但是这样是有重复的,如图。

点分治专题——bzoj 1468 &bzoj 2152 题解_第1张图片

这样的话,设K=1,那么3和4这一条(假设3-2-1-2-4是符合要求的)就重复算了。于是我们在2--3--4这棵树中,把3-2-4这条路给删掉。怎么实现呢?我们调用左下角的小树,给d[3]一个初始值path[1][2]。这样我们可以得到d[3]=path[1][2]+path[2][3],d[4]=path[1][2]+path[2][4]。如果3-2-1-2-4符合要求,这当然也符合。这样就可以成功地删掉了。

【代码】

#include<cstdio>
#include<algorithm>
#define N 40005
using namespace std;
struct arr{int s,go,next;}a[N*2];
int end[N],son[N],f[N],d[N],data[N];
int cnt,L,All,ans,i,x,y,z,n,root,K;
bool Can[N];
void add(int u,int v,int s)
{
  a[++cnt].go=v;a[cnt].next=end[u];a[cnt].s=s;end[u]=cnt;
}
void Get_root(int k,int fa)
{
  son[k]=1;f[k]=0;
  for (int i=end[k];i;i=a[i].next)
  {
    int go=a[i].go;
    if (go==fa||Can[go]) continue;
    Get_root(go,k);son[k]+=son[go];
    if (son[go]>f[k]) f[k]=son[go];
  }
  if (All-son[k]>f[k]) f[k]=All-son[k];
  if (f[k]<f[root]) root=k;
}
void Get_array(int k,int fa)
{
  data[++L]=d[k];
  for (int i=end[k];i;i=a[i].next)
  {
    int go=a[i].go;
    if (go!=fa&&!Can[go]) 
      d[go]=d[k]+a[i].s,Get_array(go,k);
  }
}
int calc(int k,int now)
{
  d[k]=now;L=0;Get_array(k,-1);
  int A=0,l,r;
  sort(data+1,data+L+1);
  for (l=1,r=L;l<r;)
    if (data[r]+data[l]<=K) A+=(r-l),l++;else r--;
  return A;
}
void work(int k)
{
  ans+=calc(k,0);Can[k]=1;
  for (int i=end[k];i;i=a[i].next)
  {
    int go=a[i].go;
    if (Can[go]) continue;
    ans-=calc(go,a[i].s);f[root=0]=n+1;
    All=son[go];Get_root(go,-1);
    work(root);
  }
}
int main()
{
  scanf("%d",&n);
  for (i=1;i<n;i++)
    scanf("%d%d%d",&x,&y,&z),add(x,y,z),add(y,x,z);
  scanf("%d",&K);All=n;
  f[root=0]=n+1;Get_root(1,-1);
  work(root);
  printf("%d",ans);
  return 0;
}

【T2——BZOJ2152】

2152: 聪聪可可

Time Limit: 3 Sec   Memory Limit: 259 MB
Submit: 335   Solved: 178

Description

聪聪和可可是兄弟俩,他们俩经常为了一些琐事打起来,例如家中只剩下最后一根冰棍而两人都想吃、两个人都想玩儿电脑(可是他们家只有一台电脑)……遇到这种问题,一般情况下石头剪刀布就好了,可是他们已经玩儿腻了这种低智商的游戏。他们的爸爸快被他们的争吵烦死了,所以他发明了一个新游戏:由爸爸在纸上画n个“点”,并用n-1条“边”把这n个“点”恰好连通(其实这就是一棵树)。并且每条“边”上都有一个数。接下来由聪聪和可可分别随即选一个点(当然他们选点时是看不到这棵树的),如果两个点之间所有边上数的和加起来恰好是3的倍数,则判聪聪赢,否则可可赢。聪聪非常爱思考问题,在每次游戏后都会仔细研究这棵树,希望知道对于这张图自己的获胜概率是多少。现请你帮忙求出这个值以验证聪聪的答案是否正确。

Input

输入的第1行包含1个正整数n。后面n-1行,每行3个整数x、y、w,表示x号点和y号点之间有一条边,上面的数是w。

Output

以即约分数形式输出这个概率(即“a/b”的形式,其中a和b必须互质。如果概率为1,输出“1/1”)。

Sample Input

5
1 2 1
1 3 2
1 4 1
2 5 3

Sample Output

13/25
【样例说明】
13组点对分别是(1,1) (2,2) (2,3) (2,5) (3,2) (3,3) (3,4) (3,5) (4,3) (4,4) (5,2) (5,3) (5,5)。

【数据规模】
对于100%的数据,n<=20000。

【分析】可能这道更简单吧,不用快排,也不用去重。我们对于当前的树G,若找到的重心是K,我们就从K出发,搜索与K相邻的点,寻找边长的余数分别是是0,1,2的情况数,然后分情况讨论。

【代码】

#include<cstdio>
#define N 50005
using namespace std;
struct arr{int s,go,next;}a[N*2];
int son[N],f[N],end[N],F[N][4];bool Can[N];
int root,n,i,All,ans,Mon,Son,c,cnt,x,y,z;
void add(int u,int v,int s)
{
  a[++cnt].go=v;a[cnt].next=end[u];a[cnt].s=s;end[u]=cnt;
}
void Get(int k,int fa)
{
  son[k]=1;f[k]=0;
  for (int i=end[k];i;i=a[i].next)
  {
    int go=a[i].go;
    if (fa==go||Can[go]) continue;
    Get(go,k);son[k]+=son[go];if (son[go]>f[k]) f[k]=son[go];
  }
  if (All-son[k]>f[k]) f[k]=All-son[k];
  if (f[k]<f[root]) root=k;
}
void dfs(int k,int fa,int P,int now)
{
  F[P][now]++;
  for (int i=end[k];i;i=a[i].next)
  {
    int go=a[i].go;
    if (go==fa||Can[go]) continue;
    dfs(go,k,P,(now+a[i].s)%3);
  }
}
void work(int k)
{
  Can[k]=1;
  for (int i=end[k];i;i=a[i].next)
  {
    int go=a[i].go;if (Can[go]) continue;
    int N0=F[k][0],N1=F[k][1],N2=F[k][2];
    dfs(go,k,k,a[i].s);
    ans+=(N0+1)*(F[k][0]-N0)+N2*(F[k][1]-N1)+N1*(F[k][2]-N2);
    f[root=0]=n+1;All=son[go];
    Get(go,-1);work(go);
  }
}
int gcd(int a,int b){return (a%b)?gcd(b,a%b):b;}
int main()
{
  scanf("%d",&n);
  if (n==0) {printf("0/1");return 0;}
  for (i=1;i<n;i++)
    scanf("%d%d%d",&x,&y,&z),add(x,y,z%3),add(y,x,z%3);
  f[root=0]=n+1;All=n;
  Get(1,-1);work(root);
  Mon=n*n;Son=ans*2+n;c=gcd(Mon,Son);Mon/=c;Son/=c;
  printf("%d/%d\n",Son,Mon);
  return 0;
}

你可能感兴趣的:(题解,bzoj,点分治)