hdu 5664 Lady CA and the graph(树的点分治+容斥)
题意:
给你一个有n个点的树,给定根,叫你找第k大的特殊链 。
特殊的链的定义:u,v之间的路径,经过题给的根节点.
题解:(来自BC官方题解)
对于求第k大的问题,我们可以通过在外层套一个二分,将其转化为求不小于mid的有多少个的问题。
接下来我们讨论如何求树上有多少条折链的长度不小于k。
我们考虑常规的点分治(对于重心,求出其到其他点的距离,排序+单调队列),时间复杂度为O(nlog^2n),但是这只能求出普通链的数量。
我们考虑将不属于折链的链容斥掉。也即,我们需要求出有多少条长度不小于mid的链,满足一端是另一端的祖先。设有一条连接u,v的链,u是v的祖先。
我们设d[i]为从根到i的链的长度,然后枚举v,然后计算在从根到v的链上,有多少个点i满足d[v]−dist[i]≥mid
我们可以按照dfs序访问各结点,动态维护从根到其的链上各d值构成的权值树状数组,就能够计算这种链的数量。时间复杂度为O(nlogn)。 因此求长度不小于mid的折链数量可以在O(nlog2n)的时间复杂度内完成。再套上最外层的二分,总时间复杂度为O(nlog3n)。
n的范围是50000,时限6s,卡常数就过去了(本行划线 由于在点分治中,复杂度中第二个logn的瓶颈在于排序。由于每次排序都是对相同的数排序,因此我们可以考虑将点分治+排序作为预处理,每次二分的时候只要做单调队列部分即可。
上述做法的总时间复杂度为O(nlog2n)。
1 #include<bits/stdc++.h> 2 #define F(i,a,b) for(int i=a;i<=b;i++) 3 using namespace std; 4 const int N=1e5+7; 5 6 int T,n,m,k,g[N],nxt[N],v[N*2],w[N*2],ed,C[N*2],hsh_ed,hsh[2*N]; 7 int sz[N],vis[N],mx[N],mi,ROOT,root,idx,ret,cnt; 8 vector<int>G[N*15],G_rt[N]; 9 10 void adg(int x,int y,int c){v[++ed]=y,w[ed]=c,nxt[ed]=g[x],g[x]=ed;} 11 inline void up(int &a,int b){if(a<b)a=b;} 12 13 inline void add(int x,int c){while(x<=hsh_ed)C[x]+=c,x+=x&-x;} 14 inline int ask(int x){int an=0;while(x>0)an+=C[x],x-=x&-x;return an;} 15 inline int getid(int x){return lower_bound(hsh+1,hsh+1+hsh_ed,x)-hsh;} 16 17 void get_rt(int u,int fa,int num) 18 { 19 sz[u]=1,mx[u]=0; 20 for(int i=g[u];i;i=nxt[i]) 21 if(v[i]!=fa&&!vis[v[i]]) 22 get_rt(v[i],u,num),sz[u]+=sz[v[i]],up(mx[u],sz[v[i]]); 23 up(mx[u],num-sz[u]); 24 if(mx[u]<mi)mi=mx[u],root=u; 25 } 26 27 void get_dis(int u,int fa,int dis) 28 { 29 G[cnt].push_back(dis); 30 for(int i=g[u];i;i=nxt[i]) 31 if(v[i]!=fa&&!vis[v[i]]) 32 get_dis(v[i],u,dis+w[i]); 33 } 34 35 void init_cal(int u,int dis) 36 { 37 cnt++,get_dis(u,u,dis); 38 sort(G[cnt].begin(),G[cnt].end()); 39 } 40 41 void get_all(int u) 42 { 43 init_cal(u,0),vis[u]=1; 44 for(int i=g[u];i;i=nxt[i]) 45 if(!vis[v[i]]) 46 { 47 init_cal(v[i],w[i]); 48 mi=sz[v[i]],get_rt(v[i],v[i],sz[v[i]]); 49 G_rt[u].push_back(root); 50 get_all(root); 51 } 52 } 53 54 void init_tree() 55 { 56 F(i,1,n)G_rt[i].clear(); 57 F(i,1,10*n)G[i].clear(); 58 memset(vis,0,sizeof(vis)); 59 mi=n,get_rt(1,1,n),ROOT=root; 60 cnt=0,get_all(ROOT); 61 } 62 //----------------以上为预处理-------- 63 64 int cal(int mid) 65 { 66 int an=0; 67 int i=0,j=G[++idx].size()-1; 68 while(i<j) 69 { 70 while(j>i&&G[idx][j]+G[idx][i]<mid)i++; 71 an+=j-i,j--; 72 } 73 return an; 74 } 75 76 void work(int u,int mid)//求出所有的链 77 { 78 ret+=cal(mid),vis[u]=1; 79 int sz=G_rt[u].size()-1; 80 F(i,0,sz)ret-=cal(mid),work(G_rt[u][i],mid); 81 } 82 83 void dfs(int u,int fa,int dis,int mid)//将每个点过根的距离计算出来 84 { 85 hsh[++hsh_ed]=dis,hsh[++hsh_ed]=dis-mid; 86 for(int i=g[u];i;i=nxt[i]) 87 if(v[i]!=fa)dfs(v[i],u,dis+w[i],mid); 88 } 89 90 void get_ret(int u,int fa,int dis,int mid)//容斥不经过根节点的答案 91 { 92 int x=getid(dis-mid),y=getid(dis); 93 ret-=ask(x),add(y,1); 94 for(int i=g[u];i;i=nxt[i]) 95 if(v[i]!=fa) 96 get_ret(v[i],u,dis+w[i],mid); 97 add(y,-1); 98 } 99 100 int check(int mid) 101 { 102 ret=0,memset(vis,0,sizeof(vis)); 103 idx=0,work(ROOT,mid); 104 hsh_ed=0,dfs(m,m,0,mid); 105 sort(hsh+1,hsh+1+hsh_ed); 106 hsh_ed=unique(hsh+1,hsh+1+hsh_ed)-hsh-1; 107 get_ret(m,0,0,mid); 108 if(ret>=k)return 1; 109 return 0; 110 } 111 112 int main() 113 { 114 scanf("%d",&T); 115 while(T--) 116 { 117 scanf("%d%d%d",&n,&m,&k); 118 int maxdis=0; 119 memset(g,0,sizeof(g)),ed=0; 120 F(i,1,n-1) 121 { 122 int x,y,c; 123 scanf("%d%d%d",&x,&y,&c); 124 adg(x,y,c),adg(y,x,c); 125 up(maxdis,c); 126 } 127 init_tree(); 128 int l=0,r=maxdis*n,ans=0,mid; 129 while(l<=r) 130 { 131 mid=l+r>>1; 132 if(check(mid))ans=mid,l=mid+1; 133 else r=mid-1; 134 } 135 if(!ans)puts("NO"); 136 else printf("%d\n",ans); 137 } 138 return 0; 139 }