树上的点对 题解

【题意】

给定一棵边带权的无根树,求树上距离不超过k的无序互异点对的个数。

【解法】

树分治练手题……

三种分治,点分治,边分治,链分治,都可以。

点分治应该也不难写,然而懒得写平衡树了,就用的相对好想好写的边分治。(因为感觉点分治需要用平衡树……)

点分治是选重心当根节点,然后统计经过根节点的路径数。边分治则是直接找一个切断后能使两边的树尽量平衡的边,统计经过边的路径数。

具体写的时候把两边的距离分别扔到两个vector里,然后二分即可。或者直接根据单调性线性扫描。

统计路径的时候如果线性扫描的话,那么复杂度是O(nlogn),好像比点分治的O(nlog2n)快一些。然而由于常数限制,实际上还是很慢的。

最坏情况下递归深度会达到O(n)(比如菊花形的树),所以要通过插入白点的方式改进最坏复杂度。通过在点到儿子的路径上插入白点来把每个点的儿子个数降到<=2,这样每个点的度数均不超过3,递归的最大深度为O(logn)。

由于建成的树形似线段树,所以树的规模最多扩大一倍,只是常数问题。总复杂度还是O(nlogn)。

第一次写树分治,写的比较丑……凑合看吧。

  1 #include<cstdio>
  2 #include<cstring>
  3 #include<algorithm>
  4 #include<vector>
  5 #include<queue>
  6 using namespace std;
  7 const int maxn=50010;
  8 struct edge{
  9     int to,w,prev;
 10     bool vis;
 11 }e[maxn<<1];
 12 void predfs(int,int);//预处理,添白点
 13 void addedge(int,int,int);
 14 void solve(int);
 15 void dfs(int,int);//算size
 16 void dfs1(int,int,int&);//找用来分治的边
 17 void dfs2(int,int,int,vector<int>&);//算距离
 18 int n,k,last[maxn],len,size[maxn],siz,tmp,ans,du[maxn],x,y,z;
 19 bool white[maxn]={false};
 20 vector<int>a,b;
 21 vector<pair<int,int> >G[maxn];
 22 int main(){
 23 #define MINE
 24 #ifdef MINE
 25     freopen("poj1741_tree.in","r",stdin);
 26     freopen("poj1741_tree.out","w",stdout);
 27 #endif
 28     while(scanf("%d%d",&n,&k)==2&&n&&k){
 29         memset(last,-1,sizeof(last));
 30         memset(white,0,sizeof(white));
 31         memset(du,0,sizeof(du));
 32         for(int i=1;i<=20000;i++)G[i].clear();
 33         len=0;
 34         for(int i=1;i<n;i++){
 35             scanf("%d%d%d",&x,&y,&z);
 36             G[x].push_back(make_pair(y,z));
 37             G[y].push_back(make_pair(x,z));
 38         }
 39         ans=0;
 40         predfs(1,0);
 41         solve(1);
 42         printf("%d\n",ans);
 43     }
 44 #ifndef MINE
 45     printf("\n--------------------DONE--------------------\n");
 46     for(;;);
 47 #endif
 48     return 0;
 49 }
 50 void predfs(int x,int p){
 51     int cnt=G[x].size();
 52     for(int i=0;i<cnt;i++)if(G[x][i].first!=p)predfs(G[x][i].first,x);
 53     queue<pair<int,int> >q;
 54     for(int i=0;i<cnt;i++)if(G[x][i].first!=p)q.push(G[x][i]);
 55     cnt=q.size();
 56     while(cnt>2){
 57         pair<int,int>a=q.front();q.pop();
 58         pair<int,int>b=q.front();q.pop();
 59         int y=++n;
 60         white[y]=true;
 61         addedge(y,a.first,a.second);
 62         addedge(a.first,y,a.second);
 63         addedge(y,b.first,b.second);
 64         addedge(b.first,y,b.second);
 65         q.push(make_pair(y,0));
 66         cnt--;
 67     }
 68     while(!q.empty()){
 69         addedge(x,q.front().first,q.front().second);
 70         addedge(q.front().first,x,q.front().second);
 71         q.pop();
 72     }
 73 }
 74 void addedge(int x,int y,int z){
 75     e[len].to=y;
 76     e[len].w=z;
 77     e[len].prev=last[x];
 78     last[x]=len++;
 79 }
 80 void solve(int x){
 81     tmp=~(1<<31);
 82     int id=-1;
 83     dfs(x,0);
 84     siz=size[x];
 85     dfs1(x,0,id);
 86     if(id==-1)return;
 87     e[id].vis=e[id^1].vis=true;
 88     solve(e[id].to);
 89     solve(e[id^1].to);
 90     a.clear();
 91     b.clear();
 92     dfs2(e[id].to,0,0,a);
 93     dfs2(e[id^1].to,0,0,b);
 94     sort(a.begin(),a.end());
 95     sort(b.begin(),b.end());
 96     int cnta=a.size(),cntb=b.size();
 97     for(int i=0,j=cntb-1;i<cnta;i++){
 98         while(~j&&k-a[i]-e[id].w<b[j])j--;
 99         ans+=j+1;
100     }
101     e[id].vis=e[id^1].vis=false;
102 }
103 void dfs(int x,int p){
104     size[x]=1;
105     for(int i=last[x];i!=-1;i=e[i].prev)if(e[i].to!=p&&!e[i].vis){
106         dfs(e[i].to,x);
107         size[x]+=size[e[i].to];
108     }
109 }
110 void dfs1(int x,int p,int &id){
111     for(int i=last[x];i!=-1;i=e[i].prev)if(e[i].to!=p&&!e[i].vis){
112         if(abs(size[e[i].to]-(siz-size[e[i].to]))<tmp){
113             id=i;
114             tmp=abs(size[e[i].to]-(siz-size[e[i].to]));
115         }
116         dfs1(e[i].to,x,id);
117     }
118 }
119 void dfs2(int x,int p,int d,vector<int>&a){
120     if(!white[x])a.push_back(d);
121     for(int i=last[x];i!=-1;i=e[i].prev)if(e[i].to!=p&&!e[i].vis)dfs2(e[i].to,x,d+e[i].w,a);
122 }

【后记】

第一道树分治,就这样献给边分治了……

看好多神犇用的都是点分治,感觉自己真奇葩……

尽头和开端,总有一个在等你。

时间: 2024-10-13 07:03:59

树上的点对 题解的相关文章

SPOJ GSS系列 最大子段和 线段树+树链剖分+splay 1043 1557 1716 2713 2916 4487 6779

最大子段和的各种形式 题解内附每道题的 题意 题目链接 思路 SPOJ 1043 GSS1 静态区间求个最大子段和, 题解 SPOJ 1577 GSS2 和1一样,区别是若区间内存在相同的元素,则该元素只计算一次. 离线一下然后使劲跑.. 题解 SPOJ 1716 GSS3 和1一样,就是要支持单点修改 题解 SPOJ 2713 GSS4 ==普通线段树,感觉和这系列关系不大. 题解 SPOJ 2916 GSS5 题意有点怪,,跟3差不多,就是稍加强了点询问 题解 SPOJ 4487 GSS6

【题解】[CJOI2019] 树上查询(树状数组)

[题解][CJOI2019] 树上查询(树状数组) 题目描述 班?小 A 需要管理信息组的日常纪律.所有人都在树形机房学习,树形机房的根节点为 1 .信息组的同学很多,但树 形机房的每个节点上有且仅有一个同学.小 A 的位置在树形机房的某个节点上,他想要管理在他所站节点子树中的同学(不算自己).每个位置都有一个管理容易值\(w_i\) ,他能管理到某个位置 ,当且仅当他们的距离不超过\(w_i\). 定位一个根\(x\),现在就是求\(u\in S\),\(S\)是\(x\)的所有子树所有节点集

【题解】 bzoj4033: [HAOI2015]树上染色* (动态规划)

bzoj4033,懒得复制,戳我戳我 Solution: 定义状态\(dp[i][j]\)表示\(i\)号节点为根节点的子树里面有\(j\)个黑色节点时最大的贡献值 然后我们要知道的就是子节点到根节点这条边会计算次数就是:子树中白色节点数\(*\)子树外白色节点数\(+\)子树中黑色节点数\(*\)子树外黑色节点数 \[dp[u][j+k]=max(dp[u][j+k],\] \[dp[u][j]+dp[v][k]+(1ll)*k*(m-k)*dis[v]+(1ll)*(siz[v]-k)*(n

FJUT 聪明的商人(树上倍增)题解

思路:求树上两点的距离,显然是dep[u] + dep[v] - 2 * dep[lca],用树上倍增去写. 参考:树上倍增的写法和应用(详细讲解,新手秒懂) 代码: #include<set> #include<map> #include<stack> #include<cmath> #include<queue> #include<vector> #include<cstdio> #include<cstring

SPOJ COT Count on a tree(树上主席树 + LCA 求路径第k小)题解

题意:n个点的树,每个点有权值,问你u~v路径第k小的点的权值是? 思路: 树上主席树就是每个点建一棵权值线段树,具体看JQ博客,LCA用倍增logn求出,具体原理看这里 树上主席树我每个点的存的是点u到源点1的权值线段树,那我求点u到v的所有点,显然是 u + v - lca - fa[lca],就是u到1 + v到1 - 多算的lca - 多算的fa[lca].不能减去两个lca不然少一个点了, LCA板子: //LCA int fa[maxn][20]; int dep[maxn]; vo

树上差分:闇の連鎖 题解

题目描述 传说中的暗之连锁被人们称为 Dark. Dark 是人类内心的黑暗的产物,古今中外的勇者们都试图打倒它. 经过研究,你发现 Dark 呈现无向图的结构,图中有 N 个节点和两类边,一类边被称为主要边,而另一类被称为附加边. Dark 有 N – 1 条主要边,并且 Dark 的任意两个节点之间都存在一条只由主要边构成的路径. 另外,Dark 还有 M 条附加边. 你的任务是把 Dark 斩为不连通的两部分. 一开始 Dark 的附加边都处于无敌状态,你只能选择一条主要边切断. 一旦你切

树上数数 题解

一.题目: 题目链接 个人私题,请注意版权保护. 二.思路: 树上莫队经典例题.其莫队的思想也很经典. 首先考虑在树上怎样莫队.我们很熟悉在序列上进行莫队,那么我们考虑将树转化成序列.那么什么序列能将子树转化为一个区间呢?当然是dfs序了! 然后回忆HH的项链那道经典题.它问的是"一段区间所包含的不同颜色的数量",我们开桶cnt[i]保存i颜色出现的数量.而这道题我们问的是"出现次数大于等于K的颜色数量",cnt数组还是要有的,还需要什么?我们很自然地想到用某个数据

[题解/模板]luogu_P3942_(树上覆盖问题

抄题解 把点按深度排序,用near数组记录到每个点最近的关键点的距离,每次取出一个点更新一下near数组,如果不能被覆盖就在它的k级祖先建立关键点,并更新所有k级祖先的k级祖先的near数组 #include<bits/stdc++.h> using namespace std; const int maxn=100009; int n,k,t,ans; struct edge{ int v,nxt; }e[maxn<<1]; int head[maxn],cnt; inline

CSP2019 树上的数 题解

题面 这是一道典型的部分分启发正解的题. 所以我们先来看两个部分分. Part 1 菊花图 这应该是除了暴力以外最好想的一档部分分了. 如上图(节点上的数字已省略),如果我们依次删去边(2)(1)(3)(4),那么操作完后2号点上的数字就会跑到1号点上,1号点数字会跑到3号点上,3号点数字跑到4号点上--依此累推.那么我们相当于把五个节点连成了一个环( 5 -> 2 -> 1 -> 3 -> 4 -> 5 ),每一个结点上的数字都会跑到环上的下一个结点上去,我们就是要求能使最