点分治专题——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)的扫描算出点对数。但是这样是有重复的,如图。

这样的话,设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 1468 &bzoj 2152 题解

时间: 2024-10-11 18:50:18

点分治专题——bzoj 1468 &bzoj 2152 题解的相关文章

矩阵乘法专题1——bzoj 1297 [SCOI2009] 迷路题解

题目链接 题意:给两个长度分别为n和m的序列,现在有两种操作:1.分别选择两个序列的一个非空前缀,切两个前缀的最后一位相同,删除之,得到1分(只累计),消耗e:2.直接删除两个序列,消耗值定于两个序列之前删除的元素个数之和,并且使得得到的分有效(之前没有有效分) 分析: 首先,问题其实就是转化成,进行若干次操作1,然后进行操作2 还要找到一个判别标准,来评判较优的状态(贪心) 每次的消耗值比较大,其实可以计算出最大的删除次数,这个值不是很大 状态表示: 简单的,一个状态可以表示为串A的位置.串B

splay专题复习——bzoj 3224 &amp; 1862 &amp; 1503 题解

[前言]快要省选二试了.上次去被虐出翔了~~这次即便是打酱油,也要打出风采!于是暂停新东西的学习,然后开始复习以前的知识,为骗分做准备.PS:区间翻转的暂时跳过,就算学了也来不及巩固了. [BZOJ3224] 3224: Tyvj 1728 普通平衡树 Time Limit: 10 Sec  Memory Limit: 128 MB Submit: 1477  Solved: 570 Description 您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作: 1. 插入

BZOJ 1~10 精简题解

从这星期起,我开始了怒刷BZOJ的旅程.这几天刷了10道题(由于"档期"的原因,所以有几道题没打完-..捂脸--..) 精简题解: 1000 A+B Problem --.. [BeiJing2006]狼抓兔子 裸的网络流,不过data有点大...... 哈,这图的性质太好了,就是一个平面图额,并且也很容易转化成对偶图,So--spfa怒跑之-- [FJOI2007]轮状病毒 Matrix-tree定理 不过,这道题有个线性递推式:f[n] = 3 * f[n - 1] – f[I -

bzoj 3198: [Sdoi2013]spring 题解

[原题] 3198: [Sdoi2013]spring Time Limit: 40 Sec  Memory Limit: 256 MB Submit: 253  Solved: 95 Description Input Output Sample Input 3 3 1 2 3 4 5 6 1 2 3 0 0 0 0 0 0 4 5 6 Sample Output 2 HINT [题解]这道题明明是水题,坑了我两天!!!真是伤心.发现哈希都不熟练了. 首先很容易想到是2^6枚举01状态,使得1

bzoj 3246 [Ioi2013] Dreaming 题解

[原题] 3246: [Ioi2013]Dreaming Time Limit: 10 Sec  Memory Limit: 64 MB Submit: 194  Solved: 68 Description Serpent(水蛇)生活的地方有N个水坑,编号为0,...,N - 1,有M条双向小路连接这些水坑.每两个水坑之间至多有一条路径(路径包含一条或多条小路)相互连接,有些水坑之间根本无法互通(即M ≤ N-1 ).Serpent走过每条小路需要一个固定的天数,不同的小路需要的天数可能不同.

bzoj 3333: 排队计划 题解

[原题] 3333: 排队计划 Time Limit: 20 Sec  Memory Limit: 128 MB Submit: 161  Solved: 71 [Submit][Status] Description Input Output Sample Input 6 2 160 163 164 161 167 160 2 3 Sample Output 6 3 1 HINT Source wyx528命题 [分析]简述一下题目.N个数排成一列,每次指定一个位置P,然后把P~N中所有身高小

BZOJ 1191 超级英雄 Hero 题解

BZOJ 1191 超级英雄 Hero 题解 Description 现在电视台有一种节目叫做超级英雄,大概的流程就是每位选手到台上回答主持人的几个问题,然后根据回答问题的多少获得不同数目的奖品或奖金.主持人问题准备了若干道题目,只有当选手正确回答一道题后,才能进入下一题,否则就被淘汰.为了增加节目的趣味性并适当降低难度,主持人总提供给选手几个“锦囊妙计”,比如求助现场观众,或者去掉若干个错误答案(选择题)等等. 这里,我们把规则稍微改变一下.假设主持人总共有m道题,选手有n种不同的“锦囊妙计”

【BZOJ 1468】Tree 点分治

点分治$O(nlogn)$ 坚持到月考结束后新校就剩下我一个OIer,其他人早已停课了,老师估计懒得为我一个人开机房门,让我跟班主任说了一声,今晚就回到了老校,开始了自己都没有想到会来的这么早的停课生活. 所以先水一道点分治 #include<cstdio> #include<algorithm> #define read(x) x=getint() #define N 40003 #define max(a,b) (a)>(b)?(a):(b) using namespac

bzoj 1468 Tree(点分治模板)

1468: Tree Time Limit: 10 Sec  Memory Limit: 64 MBSubmit: 1527  Solved: 818[Submit][Status][Discuss] Description 给你一棵TREE,以及这棵树上边的距离.问有多少对点它们两者间的距离小于等于K Input N(n<=40000) 接下来n-1行边描述管道,按照题目中写的输入 接下来是k Output 一行,有多少对点之间的距离小于等于k Sample Input 7 1 6 13 6