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(nlog2​​n)的时间复杂度内完成。再套上最外层的二分,总时间复杂度为O(nlog​3n)。

n的范围是50000,时限6s,卡常数就过去了(本行划线 由于在点分治中,复杂度中第二个logn的瓶颈在于排序。由于每次排序都是对相同的数排序,因此我们可以考虑将点分治+排序作为预处理,每次二分的时候只要做单调队列部分即可。

上述做法的总时间复杂度为O(nlog​2n)。

  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 }
View Code

 

posted @ 2016-11-28 21:34  bin_gege  阅读(370)  评论(0编辑  收藏  举报