树上的点对
【题意】
给定一棵边带权的无根树,求树上距离不超过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 }
【后记】
第一道树分治,就这样献给边分治了……
看好多神犇用的都是点分治,感觉自己真奇葩……
尽头和开端,总有一个在等你。
233333333