这道题感觉不太行 因为自己没想出来。

先说一下暴力吧,取三个点 让两两之间的距离相等怎么做呢,看起来是很复杂的样子的,但是仔细观察发现 答案出自一个点的儿子之间 或者儿子和父亲之间。

暴力枚举三个点然后 算两两点的距离 ST表的话 可以做到n^3 。

考虑 稍微暴力一点的解法 我们发现对于每个点我们统计的都是它的子树内部的答案和各个子树之间的答案以及各个子树之间及父亲之间的答案。

考虑枚举每一个点为中心 然后利用子树统计答案 具体我们发现这其实就是 完成了上述的过程。

复杂度n^2 。可以通过此题。非常的巧妙。比较暴力的解题。

//#include<bits/stdc++.h>
#include<iomanip>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<queue>
#include<deque>
#include<cmath>
#include<ctime>
#include<cstdlib>
#include<stack>
#include<algorithm>
#include<vector>
#include<cctype>
#include<utility>
#include<set>
#include<bitset>
#include<map>
#define INF 1000000000
#define ll long long
#define min(x,y) ((x)>(y)?(y):(x))
#define max(x,y) ((x)>(y)?(x):(y))
#define RI register ll
#define db double
#define EPS 1e-8
using namespace std;
char buf[<<],*fs,*ft;
inline char getc()
{
return (fs==ft&&(ft=(fs=buf)+fread(buf,,<<,stdin),fs==ft))?:*fs++;
}
inline int read()
{
int x=,f=;char ch=getc();
while(ch<''||ch>''){if(ch=='-')f=-;ch=getc();}
while(ch>=''&&ch<=''){x=x*+ch-'';ch=getc();}
return x*f;
}
const int MAXN=;
int n,len,maxx;
int d[MAXN];
ll ans,f1[MAXN],f2[MAXN],f[MAXN];
int lin[MAXN],ver[MAXN<<],nex[MAXN<<];
inline void add(int x,int y)
{
ver[++len]=y;
nex[len]=lin[x];
lin[x]=len;
}
inline void dfs(int x,int father)
{
d[x]=d[father]+;++f[d[x]];
maxx=max(maxx,d[x]);
for(int i=lin[x];i;i=nex[i])
{
int tn=ver[i];
if(tn==father)continue;
dfs(tn,x);
}
}
int main()
{
freopen("1.in","r",stdin);
n=read();
for(int i=;i<n;++i)
{
int x,y;
x=read();y=read();
add(x,y);add(y,x);
}
for(int i=;i<=n;++i)
{
memset(f1,,sizeof(f1));
memset(f2,,sizeof(f2));
for(int j=lin[i];j;j=nex[j])
{
int tn=ver[j];
maxx=;d[i]=;
dfs(tn,i);
for(int k=;k<=maxx;++k)
{
ans+=f2[k]*f[k];
f2[k]+=f[k]*f1[k];
f1[k]+=f[k];f[k]=;
}
}
}
printf("%lld\n",ans);
return ;
}

当然也有我的原始思路 树形dp一下 f[i][j] 表示以i为根距i距离为j的点的个数 这个很好求f[i][j]=f[tn][j-1];f[x][0]=1;

考虑如何统计答案 在这个地方我遇到了一点小困难显然的是 答案出自自己的子树和子树和父亲之间 至于子树内部的东西我们可以递归来求解。

如何统计答案是一个重难点,这里有一个比较神仙了状态我也没有想出来想要统计答案必然的我们要先得到 距离i为j的点对的个数再用单个点的个数来计算。

设g[i][j]表示点对 直接到LCA的距离为d 到i这个点距离为d-j的个数 看起来非常的绕 但是 也比较自然因为这样才能与我们的f[i][j] 相结合起来组成答案。

一些 细节没有考虑清楚导致 wa 了很多次 我在进行统计答案的时候不光只有根的g数组*子树的f数组 还应该有子树的g数组*根的f数组(这一步代表子树和子树之间是双向的。

当然其中也有到根的转移 故父亲的那个地方也考虑到了 所以 是正确的。

//#include<bits/stdc++.h>
#include<iomanip>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<queue>
#include<deque>
#include<cmath>
#include<ctime>
#include<cstdlib>
#include<stack>
#include<algorithm>
#include<vector>
#include<cctype>
#include<utility>
#include<set>
#include<bitset>
#include<map>
#define INF 1000000000
#define ll long long
#define min(x,y) ((x)>(y)?(y):(x))
#define max(x,y) ((x)>(y)?(x):(y))
#define RI register ll
#define db double
#define EPS 1e-8
using namespace std;
char buf[<<],*fs,*ft;
inline char getc()
{
return (fs==ft&&(ft=(fs=buf)+fread(buf,,<<,stdin),fs==ft))?:*fs++;
}
inline int read()
{
int x=,f=;char ch=getc();
while(ch<''||ch>''){if(ch=='-')f=-;ch=getc();}
while(ch>=''&&ch<=''){x=x*+ch-'';ch=getc();}
return x*f;
}
const int MAXN=;
int n,len,maxx;
int d[MAXN];
ll ans;
ll f[MAXN][MAXN];//f[i][j]表示距i点距离为j时的点的个数显然有f[i][j]+=f[tn][j-1];
ll g[MAXN][MAXN];//g[i][j]表示点对距LCA的距离为d时距i点距离为d-j时的点对个数
//首先这里说明这个状态的必要性 子树内部的答案是递归处理的这个先不管
//自己子树与子树之间的答案 利用g[i][j]*f[i][j]来计算
//那么子树和父亲呢 显然 g[i][0] 就是讨论与父亲之间的答案的
//综上 解决的答案的统计 证毕。
//状态空间包涵整个问题 看起还很妙的样子。
int lin[MAXN],ver[MAXN<<],nex[MAXN<<];
inline void add(int x,int y)
{
ver[++len]=y;
nex[len]=lin[x];
lin[x]=len;
}
inline void dfs(int x,int father)
{
maxx=max(maxx,d[x]);
for(int i=lin[x];i;i=nex[i])
{
int tn=ver[i];
if(tn==father)continue;
d[tn]=d[x]+;
dfs(tn,x);
}
}
inline void dp(int x,int father)
{
f[x][]=;
for(int i=lin[x];i;i=nex[i])
{
int tn=ver[i];
if(tn==father)continue;
dp(tn,x);
for(int j=maxx;j>=;--j)
{
if(j->=)
{
ans+=g[x][j]*f[tn][j-];
ans+=g[tn][j]*f[x][j-];
g[x][j]+=f[x][j]*f[tn][j-];
f[x][j]+=f[tn][j-];
}
g[x][j]+=g[tn][j+];
}
}
//ans+=g[x][0];
}
int main()
{
freopen("1.in","r",stdin);
n=read();
for(int i=;i<n;++i)
{
int x,y;
x=read();y=read();
add(x,y);add(y,x);
}
dfs(,);
dp(,);
printf("%lld\n",ans);
return ;
}

此题n<=5000 如果n是100000呢 怎么办n^2 挂掉的话我们 需要再次优化。

这就引入了我们经典的树上优化 长链刨分: 按照深度 划分轻重链 。

性质 1 所有链长的和是O(n)的。证明:所有点都在一条重链中 只被计算一次 因为链长总和是O(n);

性质 2 一个点的k次祖先y所在链的长度>=k 显然

性质 3 一个点向上跳重链的次数不超过sqrt(n) 显然 ->1+2+3+...sqrt(n) 总和 *2>n;

有了这些性质我可以开始长链剖分 对于以上的问题,我们进行完长链刨分以后我们钦定 选取重儿子直接转移信息 轻儿子暴力转移信息。

那么 总复杂度 是 O(n) + sum(重链长度)  这样复杂度为O(n) 。

没人讲解 tmp 数组是干什么的 我也只能暂时性的意会一下。

//#include<bits/stdc++.h>
#include<iomanip>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<queue>
#include<deque>
#include<cmath>
#include<ctime>
#include<cstdlib>
#include<stack>
#include<algorithm>
#include<vector>
#include<cctype>
#include<utility>
#include<set>
#include<bitset>
#include<map>
#define INF 1000000000
#define ll long long
#define min(x,y) ((x)>(y)?(y):(x))
#define max(x,y) ((x)>(y)?(x):(y))
#define RI register ll
#define db double
#define EPS 1e-8
using namespace std;
char buf[<<],*fs,*ft;
inline char getc()
{
return (fs==ft&&(ft=(fs=buf)+fread(buf,,<<,stdin),fs==ft))?:*fs++;
}
inline int read()
{
int x=,f=;char ch=getc();
while(ch<''||ch>''){if(ch=='-')f=-;ch=getc();}
while(ch>=''&&ch<=''){x=x*+ch-'';ch=getc();}
return x*f;
}
const int MAXN=;
int n,len,maxx;
int d[MAXN],son[MAXN];
ll ans;
ll *f[MAXN],tmp[MAXN<<],*id=tmp;//f[i][j]表示距i点距离为j时的点的个数显然有f[i][j]+=f[tn][j-1];
ll *g[MAXN];//g[i][j]表示点对距LCA的距离为d时距i点距离为d-j时的点对个数
//首先这里说明这个状态的必要性 子树内部的答案是递归处理的这个先不管
//自己子树与子树之间的答案 利用g[i][j]*f[i][j]来计算
//那么子树和父亲呢 显然 g[i][0] 就是讨论与父亲之间的答案的
//综上 解决的答案的统计 证毕。
//状态空间包涵整个问题 看起还很妙的样子。
int lin[MAXN],ver[MAXN<<],nex[MAXN<<];
inline void add(int x,int y)
{
ver[++len]=y;
nex[len]=lin[x];
lin[x]=len;
}
inline void dfs(int x,int father)
{
for(int i=lin[x];i;i=nex[i])
{
int tn=ver[i];
if(tn==father)continue;
dfs(tn,x);
if(d[tn]>d[son[x]])son[x]=tn;
}
d[x]=d[son[x]]+;
}
inline void dp(int x,int father)
{
if(son[x])f[son[x]]=f[x]+,g[son[x]]=g[x]-,dp(son[x],x);
f[x][]=;ans+=g[x][];
for(int i=lin[x];i;i=nex[i])
{
int tn=ver[i];
if(tn==father||tn==son[x])continue;
f[tn]=id;id+=d[tn]<<;g[tn]=id;id+=d[tn]<<;
dp(tn,x);
for(int j=d[tn];j>=;--j)
{
if(j->=)
{
ans+=g[x][j]*f[tn][j-];
ans+=g[tn][j]*f[x][j-];
g[x][j]+=f[x][j]*f[tn][j-];
f[x][j]+=f[tn][j-];
}
if(j+<=d[tn])g[x][j]+=g[tn][j+];
}
}
}
int main()
{
freopen("1.in","r",stdin);
n=read();
for(int i=;i<n;++i)
{
int x,y;
x=read();y=read();
add(x,y);add(y,x);
}
dfs(,);
f[]=id;id+=d[]<<;g[]=id;id+=d[]<<;
dp(,);
printf("%lld\n",ans);
return ;
}

这 就是O(n) 的长链剖分 加速dp

P3565 由简单的树形dp 引入 长链刨分的更多相关文章

  1. 【树形dp 最长链】bzoj1912: [Apio2010]patrol 巡逻

    富有思维性的树形dp Description Input 第一行包含两个整数 n, K(1 ≤ K ≤ 2).接下来 n – 1行,每行两个整数 a, b, 表示村庄a与b之间有一条道路(1 ≤ a, ...

  2. LOJ3053 十二省联考2019 希望 容斥、树形DP、长链剖分

    传送门 官方题解其实讲的挺清楚了,就是锅有点多-- 一些有启发性的部分分 L=N 一个经典(反正我是不会)的容斥:最后的答案=对于每个点能够以它作为集合点的方案数-对于每条边能够以其两个端点作为集合点 ...

  3. bzoj 5210(树链刨分下做个dp)

    5210: 最大连通子块和 Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 211  Solved: 65[Submit][Status][Discus ...

  4. 简单了解树形DP

    今天在B站看了一个树形DP教学视频有所收获,做一个小小的总结 AV号和链接在这:av12194537 那么先介绍一下树形DP 树形DP就是在树这个特殊的数据结构上进行的DP.有两种方向:自顶向下和自底 ...

  5. HDU 4607 Park Visit (DP最长链)

    [题目]题意:N个城市形成一棵树,相邻城市之间的距离是1,问访问K个城市的最短路程是多少,共有M次询问(1 <= N, M <= 100000, 1 <= K <= N). [ ...

  6. HDU 4612 Warm up (边双连通分量+DP最长链)

    [题意]给定一个无向图,问在允许加一条边的情况下,最少的桥的个数 [思路]对图做一遍Tarjan找出桥,把双连通分量缩成一个点,这样原图就成了一棵树,树的每条边都是桥.然后在树中求最长链,这样在两端点 ...

  7. 洛谷P4338 [ZJOI2018]历史(LCT,树形DP,树链剖分)

    洛谷题目传送门 ZJOI的考场上最弱外省选手T2 10分成功滚粗...... 首先要想到30分的结论 说实话Day1前几天刚刚刚掉了SDOI2017的树点涂色,考场上也想到了这一点 想到了又有什么用? ...

  8. 洛谷 P1352 没有上司的舞会【树形DP/邻接链表+链式前向星】

    题目描述 某大学有N个职员,编号为1~N.他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司.现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数Ri, ...

  9. URAL 1108 简单的树形dp背包问题

    题目大意: 一颗苹果树上,每条边都对应了一个权值,最后留下包括root : 1在的含有 m 条边的子树 , 希望留下的子树中权值之和最大 这里保留m条边,我们可以看作是保留了 m + 1 个点 令dp ...

随机推荐

  1. Bank Hacking题解

    题目: 题意: 有一颗树,你可以断开点(第一个随便断,以后只能是和已经断开的点相临的点),每个点有权值,断开之后,经一条边和两条边可以到达的节点权值加一,问到最后出现过的最大的权值. 分析: 为啥断开 ...

  2. 结合实际需求,在webapi内利用WebSocket建立单向的消息推送平台,让A页面和服务端建立WebSocket连接,让其他页面可以及时给A页面推送消息

    1.需求示意图 2.需求描述 原本是为了给做unity3d客户端开发的同事提供不定时的消息推送,比如商城购买道具后服务端将道具信息推送给客户端. 本篇文章简化理解,用“相关部门开展活动,向全市人民征集 ...

  3. day78 作业

    目录 1 在作业.html的代码基础上,完成商品数量的加减,注意商品数量如果低于0个,则自动删除当前商品 2 在作业.html的代码基础仧,完成购物车总价格的计算 3 使用ajax获取北京天气,并把昨 ...

  4. python面试题三:Python 网络编程与并发

    1 简述 OSI 七层协议. OSI七层协议模型主要是: 应用层(Application):为用户的应用程序(例如电子邮件.文件传输和终端仿真)提供网络服务. 表示层(Presentation):使用 ...

  5. Python并发编程03 /僵孤进程,孤儿进程、进程互斥锁,进程队列、进程之间的通信

    Python并发编程03 /僵孤进程,孤儿进程.进程互斥锁,进程队列.进程之间的通信 目录 Python并发编程03 /僵孤进程,孤儿进程.进程互斥锁,进程队列.进程之间的通信 1. 僵尸进程/孤儿进 ...

  6. Android 性能优化 ---- 启动优化

    Android 性能优化 ---- 启动优化 1.为什么要进行启动优化 一款应用的第一印象很重要,第一印象往往决定了用户的去留.打开一款应用,如果速度很快,很顺畅,那么很容易让人觉得这款应用背后的技术 ...

  7. unity-Timeline实践

    前言 建议入门方式(基本的手册知识了解之后):官方Demo TimelineInputDemo 自定义轨迹 CustomTrack Signal 标记 创建Emitter 和 Receiver 引用: ...

  8. Spring Boot整合swagger使用教程

    目录 Swagger的介绍 优点与缺点 添加swagger 1.添加依赖包: 2.配置Swagger: 3.测试 场景: 定义接口组 定义接口 定义接口请求参数 场景一:请求参数是实体类. 场景二:请 ...

  9. MySQL 三万字精华总结 + 面试100 问,吊打面试官绰绰有余(收藏系列)

    写在之前:不建议那种上来就是各种面试题罗列,然后背书式的去记忆,对技术的提升帮助很小,对正经面试也没什么帮助,有点东西的面试官深挖下就懵逼了. 个人建议把面试题看作是费曼学习法中的回顾.简化的环节,准 ...

  10. bootstrap中模态框如果放入form表单中会存在的问题

    bootstrap中模态框如果放入form表单中会存在的问题:当模态框显示时,点回车会出现表单自动提交!!!所以在使用模态框的时候要特别注意!