点分治:统计长度不大于K的路径条数
来源:互联网 发布:mac如何打罗马数字 编辑:程序博客网 时间:2024/05/01 22:14
树的路径统计
统计有多少条长度不大于k的路径
树的点分治:将路径分为经过重心和不经过重心的路径。
解法一:
1.统计经过重心的满足条件的路径条数。
1)统计经过重心,且路径可在重心周围有重合的满足条件的路径条数—O(n)
2)统计经过重心的子节点,且路径可在重心子节点周围有重合的满足路径条数O(n)
3)经过重心满足条件的路径条数=1)-2)中的路径条数(巧妙之处!)
2.不经过重心,将重心除去,分为x个子树,分别寻找每个子树的重心,重复上述步骤即可。由于重心的定义可知,递归层数不会超过O(logn) ,因此总体复杂度为O(n*logn)
#include<stdio.h>#include<iostream>#include<algorithm>#include<string.h>#define maxn 20010#define lowbit(x) x&(-x)#define mem(a,b) memset(a,b,sizeof(a))#define ll long long#define INF 0x3f3f3f3fusing namespace std;int n;int k;struct node{ int to,next,w;}edge[maxn*2];int head[maxn],tot;int sum,f[maxn],vis[maxn],son[maxn],core,deep[maxn],d[maxn];//getcoreint ans;void init(){ mem(head,-1); mem(vis,0); tot=0; ans=0;}void add(int a,int b,int c){ edge[tot].to=b,edge[tot].next=head[a],edge[tot].w=c; head[a]=tot++;}void getcore(int u,int fa){ son[u]=1,f[u]=0;//每次更新前必须初始化!!!---dfs可以在搜索子树前在内部对点初始化 for(int i=head[u];i!=-1;i=edge[i].next){ int v=edge[i].to; if(v==fa||vis[v]) continue;//除去已找到的重心后求子树重心 getcore(v,u); son[u]+=son[v],f[u]=max(f[u],son[v]); } f[u]=max(f[u],sum-son[u]); if(f[u]<f[core]) core=u;//core记录该树重心}void getdeep(int u,int fa){//得到u子树每个节点的深度 deep[++deep[0]]=d[u]; for(int i=head[u];i!=-1;i=edge[i].next){ int v=edge[i].to,w=edge[i].w; if(v==fa||vis[v]) continue; d[v]=d[u]+w; getdeep(v,u); }}int cal(int u,int w){//1.该点是重心:求该点子树每点到该点深度2.该点父节点是重心:求该点子树到父节点的深度 deep[0]=0,d[u]=w,getdeep(u,0); int tmp=0; sort(deep+1,deep+deep[0]+1);//<strong><span style="color:#ff0000;">技巧</span></strong> for(int l=1,r=deep[0];l<r;) if(deep[l]+deep[r]<=k) tmp+=(r-l),l++; else r--; return tmp;}void work(int u){ ans+=cal(u,0);//经过原树重心的k路径条数 vis[u]=1;//原树重心已算,标记 for(int i=head[u];i!=-1;i=edge[i].next){ int v=edge[i].to,w=edge[i].w; if(vis[v]) continue;//如果v已经作为重心枚举过,u不会遍历到v,因此不必除去 ans-=cal(v,w);//除去经过重心同一子树的路径 sum=son[v];//更改当前树节点总和 core=0; getcore(v,core); work(core); }}int main(){ //freopen("a.txt","r",stdin); while(scanf("%d%d",&n,&k)!=EOF){ if(!n&&!k) break; init(); for(int i=0;i<n-1;i++){ int a,b,c; scanf("%d%d%d",&a,&b,&c); add(a,b,c),add(b,a,c); } core=0,sum=n,f[0]=INF; getcore(1,0); work(core); printf("%d\n",ans); } return 0;}
易错点:1、sum、core、vis[u]的更新 2、边集开两倍数组大小
技巧:统计一列数和<=k的数对数量
扫描法。1)先将数从小到达排序2)每次统计左端点作为较小数时的数对对数。详见代码
解法二:
解法二在统计经过重心的路径条数时与法一略微不同。
1.统计经过重心的路径条数
1)记录以重心子节点为端点的路径,以子节点分类存储,则以重心为端点经过不同子树,且无重合的路径=该路径+dis< u,v >(重心到子节点的边长) O(n*logn)
2)统计每两个相邻的子节点路径和<=K的路径条数,再将其合并为一组,每一层复杂度为O(n),递归层数不超过logn,总复杂度为O(n*logn)
2.不经过重心,将重心除去,分为x个子树,分别寻找每个子树的重心,重复上述步骤即可。由于重心的定义可知,递归层数不会超过O(logn)
总复杂度为O(n*logn*logn)
#include<stdio.h>#include<iostream>#include<string.h>#include<algorithm>#include<limits.h>#include<queue>#include<vector>#define mem(a,b) memset(a,sizeof(a),b)#define ll long long#define MP make_pair#define MP(x,y) make_pair((x),(y))#define X first#define Y second#define oo 0x3f3f3f3fconst ll INF = 0x0fffffffffffffff;using namespace std;typedef long long lld;const int maxn = 500000+10;typedef pair<int,int> F;int n,m,K;//e存储边:X为长度,Y为目标点//a存储以重心为根时,每个子节点的边长:X为长度,Y为节点个数(不包括根节点)vector<F> e[maxn],a[maxn];int s[maxn];//子树节点个数bool vis[maxn];int ans;//计算a和b内边长和<=K的路径个数void calc(vector<F> &a,vector<F> &b){ int p=-1; for(int i=a.size()-1;i>=0;i--){ while(p+1<b.size()&&b[p+1].X+a[i].X<=K) p++; ans+=(p+1); }}void build(){ for(int i=1;i<n;i++) e[i].clear(); for(int i=0;i<m;i++){ int a,b,c; char ch[5]; scanf("%d%d%d%s",&a,&b,&c,ch); e[a].push_back(MP(c,b)); e[b].push_back(MP(c,a)); }}//求剩余连通树的重心F Find(int u,int f,int sum){ s[u]=1; int lar=0; F ans=MP(oo,0); for(int i=0;i<e[u].size();i++){ int v=e[u][i].Y; if(v==f||vis[v]) continue; F x=Find(v,u,sum); s[u]+=s[v]; lar=max(lar,s[v]);//记录子树节点的最大值 ans=min(ans,x);//记录子树结果的最小值(权值最小,然后编号最小) } lar=max(lar,sum-s[u]); ans=min(ans,MP(lar,u)); return ans;}void dfs2(int u,int f,int dis,int num,vector<F> &a){ a.push_back(MP(dis,num)); s[u]=1;//统计以u为根时子树大小 for(int i=0;i<e[u].size();i++){ int v=e[u][i].Y,c=e[u][i].X; if(v==f||vis[v]) continue; dfs2(v,u,dis+c,num+1,a); s[u]+=s[v]; }}//树中分治void dfs(int u,int f,int sum){ u=Find(u,f,sum).Y;//u是该树重心 int m=0,left,x=-1; for(int i=0;i<e[u].size();i++){//得到以重心u为根时,统计个子节点路径长度及相应的节点个数 int v=e[u][i].Y,c=e[u][i].X; if(v==f||vis[v]) continue; a[++m].clear(); dfs2(v,u,c,1,a[m]); sort(a[m].begin(),a[m].end()); } a[++m].clear(); a[left=m].push_back(MP(0,0));//建立虚拟节点 while(left>1){ int d=left/2; for(int i=1;i<=d;i++){ calc(a[i],a[i+d]); vector<F> b(a[i].size()+a[i+d].size()); //merge函数:将两个序列合并成一个序列,并使其有序化---按照最后一个数据结构的大小定义排序 merge(a[i].begin(),a[i].end(),a[i+d].begin(),a[i+d].end(),b.begin()); a[i]=b; } if(left&1) a[++d]=a[left]; left=d; } vis[u]=1; for(int i=0;i<e[u].size();i++){ int v=e[u][i].Y; if(v!=f&&!vis[v]&&s[v]>1) dfs(v,u,s[v]); }}void init(){ mem(vis,0); ans=0;}int main(){ //freopen("a.txt","r",stdin); scanf("%d%d",&n,&m); init(); build(); scanf("%d",&K); dfs(1,-1,n); printf("%d\n",ans);}
0 0
- 点分治:统计长度不大于K的路径条数
- 点分治:统计长度为K的路径条数
- 【codechef】 Prime Distance On Tree【求树上路经长度为i的路径条数】【点分治+FFT】
- hdu4862 2014多校B题/ 费用流(最优情况下用不大于K条路径覆盖)(不同的解法)
- Hdu 5314 Happy King(求树上多少个点对(u,v)满足u到v的路径上点权值最大值减最小值不大于给定的K)
- 整数划分n划分成不大于k的放法数
- 【hdu3415】【单调队列 】Max Sum of Max-K-sub-sequence【求长度不大于k的区间最大子串和】
- 逆序输出不大于五位的数
- poj 2114 树分治(是否存在长度恰为k的路径)
- 【树的分治统计点对距离<=k】POJ 1741
- 二分实现查找不小于x的第一个数/不大于x的最后一个数
- 树上路径统计——点分治の板子
- 分治-寻找第k小的数
- [线段树练习5] 线段的条数 - 统计点重复标记数
- 【BZOJ3697】采药人的路径【点分治】
- 【bzoj3697】【采药人的路径】【点分治】
- 【bzoj3697】采药人的路径 点分治
- bzoj3697 采药人的路径 点分治
- 第八周 项目三(1):分数类中的运算符重载
- 如何修改mysql 5.6.24解压缩版(免安装版或zip版)字符编码
- IIS中配置WCF站点
- sicily 1194 message blood之通过篇
- 单例模式的七种写法
- 点分治:统计长度不大于K的路径条数
- [LeetCode] Remove Duplicates from Sorted Array
- 构造器和多态
- mfc中stdafx.h
- phonegap打包签名覆盖问题
- 详解JavaScript令人困惑不解的条件判断
- 不得不说说backports
- spring Log4j
- android字体高度,宽度计算方法