BZOJ4317Atm的树&BZOJ2051A Problem For Fun&BZOJ2117[2010国家集训队]Crash的旅游计划——二分答案+动态点分治(点分树套线段树/点分树+vector)
题目描述
Atm有一段时间在虐qtree的题目,于是,他满脑子都是tree,tree,tree……
于是,一天晚上他梦到自己被关在了一个有根树中,每条路径都有边权,一个神秘的声音告诉他,每个点到其他的点有一个距离(什么是距离不用说吧),他需要对于每个点回答:从这个点出发的第k小距离是多少;
如果atm不能回答出来,那么明天4019的闹钟将不会响,4019全寝可能就迟到了,所以atm希望你帮帮他。
输入
第一行,两个正整数n,k,表示树的点数,询问的是第几小距离;
第二~n行,每行三个正整数x,y,w,表示x和y之间有一条边,x为父亲,边权为w;
输出
n行, 每行一个数,第i行输出从i开始第k小距离
样例输入
5 2
1 5 2
1 2 4
2 3 6
2 4 5
1 5 2
1 2 4
2 3 6
2 4 5
样例输出
4
5
10
9
6
5
10
9
6
提示
100% n<=15000, 边权在1~10之间,为了方便,保证1为根;K<=5000
我们先考虑对于单次询问如何用点分治来解决。
对于每个分治中心,如果它处理的联通块中包含查询点,那么需要记录下来联通块中所有点到分治中心的距离+分治中心到查询点的距离,然后容斥去掉与查询点位于分治中心同一棵子树中的点,递归下一层,最后在所有记录的距离中取第k小的。
那么转化到点分树上每个点要记录子树中所有点到这个点的距离及子树中所有点到这个点在点分树上父节点的距离,这个信息在每个点开两棵线段树分别记录一下即可。
因为需要查询多个点的信息并合并,所以不能直接求出第k小,要二分答案然后验证。
BZOJ2117线段树卡内存、平衡树卡时间,但你发现所有查询都在信息添加之后,所以每个点开两个vector然后排序,每次查询在vector上二分查找即可。
时间复杂度O(nlogn^3)。
点分树套线段树
#include<cmath> #include<vector> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define ll long long using namespace std; int root[15010]; int froot[15010]; int ls[6000010]; int rs[6000010]; int sum[6000010]; int n,k; int x,y,z; int tot; int num; int dfn; int f[15010]; int g[30010][16]; int lg[30010]; int dep[15010]; int val[30010]; int to[30010]; int next[30010]; int head[15010]; int size[15010]; int s[15010]; int rot; int cnt; int mx[15010]; int ans; int vis[15010]; int l,r; inline void add(int x,int y,int z) { tot++; next[tot]=head[x]; head[x]=tot; to[tot]=y; val[tot]=z; } inline void dfs(int x,int fa) { g[++dfn][0]=dep[x]; s[x]=dfn; for(int i=head[x];i;i=next[i]) { if(to[i]!=fa) { dep[to[i]]=dep[x]+val[i]; dfs(to[i],x); g[++dfn][0]=dep[x]; } } } inline void getroot(int x,int fa) { size[x]=1; mx[x]=0; for(int i=head[x];i;i=next[i]) { if(!vis[to[i]]&&to[i]!=fa) { getroot(to[i],x); size[x]+=size[to[i]]; mx[x]=max(mx[x],size[to[i]]); } } mx[x]=max(mx[x],num-size[x]); if(mx[x]<mx[rot]) { rot=x; } } inline int lca(int x,int y) { x=s[x]; y=s[y]; if(x>y) { swap(x,y); } int len=lg[y-x+1]; return min(g[x][len],g[y-(1<<len)+1][len]); } inline int dis(int x,int y) { return dep[x]+dep[y]-2*lca(x,y); } inline void partation(int x) { vis[x]=1; for(int i=head[x];i;i=next[i]) { if(!vis[to[i]]) { num=size[to[i]]; rot=0; getroot(to[i],0); f[rot]=x; partation(rot); } } } inline void change(int &rt,int l,int r,int k) { if(!rt) { rt=++cnt; } sum[rt]++; if(l==r) { return ; } int mid=(l+r)>>1; if(k<=mid) { change(ls[rt],l,mid,k); } else { change(rs[rt],mid+1,r,k); } } inline int query(int rt,int l,int r,int k) { if(!rt||k<0) { return 0; } if(l==r) { return sum[rt]; } int mid=(l+r)>>1; if(k<=mid) { return query(ls[rt],l,mid,k); } else { return sum[ls[rt]]+query(rs[rt],mid+1,r,k); } } inline int check(int val,int x) { int res=0; for(int i=x;i;i=f[i]) { res+=query(root[i],0,150000,val-dis(x,i)); } for(int i=x;f[i];i=f[i]) { res-=query(froot[i],0,150000,val-dis(x,f[i])); } return res; } int main() { scanf("%d%d",&n,&k); k++; for(int i=1;i<n;i++) { scanf("%d%d%d",&x,&y,&z); add(x,y,z); add(y,x,z); } dfs(1,0); for(int i=2;i<=dfn;i++) { lg[i]=lg[i>>1]+1; } for(int j=1;j<=15;j++) { for(int i=1;i<=dfn;i++) { if(i+(1<<j)-1>dfn) { break; } g[i][j]=min(g[i][j-1],g[i+(1<<(j-1))][j-1]); } } mx[0]=1<<30; num=n; rot=0; getroot(1,0); partation(rot); for(int x=1;x<=n;x++) { for(int i=x;i;i=f[i]) { change(root[i],0,150000,dis(x,i)); } for(int i=x;f[i];i=f[i]) { change(froot[i],0,150000,dis(x,f[i])); } } for(int i=1;i<=n;i++) { l=0; r=150000; ans=-1; while(l<=r) { int mid=(l+r)>>1; if(check(mid,i)>=k) { ans=mid; r=mid-1; } else { l=mid+1; } } printf("%d\n",ans); } }
点分树+vector
#include<cmath> #include<vector> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; vector<int>sum[100010]; vector<int>fsum[100010]; int n,k; int x,y,z; int tot; int num; int dfn; char ch[3]; int f[100010]; int g[200010][17]; int lg[200010]; int dep[100010]; int val[200010]; int to[200010]; int next[200010]; int head[100010]; int size[100010]; int s[100010]; int rot; int cnt; int mx[100010]; int ans; int vis[100010]; int l,r; int length; void add(int x,int y,int z) { tot++; next[tot]=head[x]; head[x]=tot; to[tot]=y; val[tot]=z; } void dfs(int x,int fa) { g[++dfn][0]=dep[x]; s[x]=dfn; for(int i=head[x];i;i=next[i]) { if(to[i]!=fa) { dep[to[i]]=dep[x]+val[i]; dfs(to[i],x); g[++dfn][0]=dep[x]; } } } void getroot(int x,int fa) { size[x]=1; mx[x]=0; for(int i=head[x];i;i=next[i]) { if(!vis[to[i]]&&to[i]!=fa) { getroot(to[i],x); size[x]+=size[to[i]]; mx[x]=max(mx[x],size[to[i]]); } } mx[x]=max(mx[x],num-size[x]); if(mx[x]<mx[rot]) { rot=x; } } int lca(int x,int y) { x=s[x]; y=s[y]; if(x>y) { swap(x,y); } int len=lg[y-x+1]; return min(g[x][len],g[y-(1<<len)+1][len]); } int dis(int x,int y) { return dep[x]+dep[y]-(lca(x,y)<<1); } void partation(int x) { vis[x]=1; for(int i=head[x];i;i=next[i]) { if(!vis[to[i]]) { num=size[to[i]]; rot=0; getroot(to[i],0); f[rot]=x; partation(rot); } } } int check(int val,int x) { int res=0; for(int i=x;i;i=f[i]) { res+=upper_bound(sum[i].begin(),sum[i].end(),val-dis(x,i))-sum[i].begin(); } for(int i=x;f[i];i=f[i]) { res-=upper_bound(fsum[i].begin(),fsum[i].end(),val-dis(x,f[i]))-fsum[i].begin(); } return res; } int main() { scanf("%s",ch); scanf("%d%d",&n,&k); k++; for(int i=1;i<n;i++) { scanf("%d%d%d",&x,&y,&z); length+=z; add(x,y,z); add(y,x,z); } dfs(1,0); for(int i=2;i<=dfn;i++) { lg[i]=lg[i>>1]+1; } for(int j=1;j<=16;j++) { for(int i=1;i<=dfn;i++) { if(i+(1<<j)-1>dfn) { break; } g[i][j]=min(g[i][j-1],g[i+(1<<(j-1))][j-1]); } } mx[0]=1<<30; num=n; rot=0; getroot(1,0); partation(rot); for(int x=1;x<=n;x++) { for(int i=x;i;i=f[i]) { sum[i].push_back(dis(i,x)); } for(int i=x;f[i];i=f[i]) { fsum[i].push_back(dis(f[i],x)); } } for(int i=1;i<=n;i++) { sort(sum[i].begin(),sum[i].end()); sort(fsum[i].begin(),fsum[i].end()); } for(int i=1;i<=n;i++) { l=0; r=length; ans=-1; while(l<=r) { int mid=(l+r)>>1; if(check(mid,i)>=k) { ans=mid; r=mid-1; } else { l=mid+1; } } printf("%d\n",ans); } }