Loading

浅谈点分治

Luogu P4178 Tree

我觉得淀粉质还是得从这道题讲起。

点分治,是一种处理树上路径问题的工具,我认为其思想还是对于子树分治处理。

这道题要求距离小于等于 \(k\) 的点对数量,考虑怎么淀粉质。因为我们很容易就可以 \(dfs\) 求出子树中的点到根节点的距离,所以我们只用找出任意两条路径小于等于 \(k\)(不需要管是不是不同在子树),再利用总情况减去不合法情况就好了。

关于点分治,因为要保证复杂度,所以应该在树的重心点分治,所以我们需要找出树的重心。

找出路径后怎么统计答案呢?可以用两个指针来统计

先让所有路径按长度从小到大排序,初始时:\(l=1,r=cnt\)

while(l<r){
  if(f[l]+f[r]<=k){//符合条件
    res+=r-l;//统计答案
    l++;//跳较小的,更容易再次满足条件
  }
  else r--;//只有跳大的了
}

代码:

#include<bits/stdc++.h>
using namespace std;
inline int read(){
  int ans=0,f=1;char ch=getchar();
  while(!isdigit(ch)){if(ch=='-') f=-f;ch=getchar();}
  while(isdigit(ch)){ans=(ans<<3)+(ans<<1)+ch-48;ch=getchar();}
  return ans*f;
}
const int N=4e4+5;
struct lzz{
  int nx,to,w;
}edge[N<<1];
int n,hd[N],tot,k,ans,cnt,vis[N],f[N],dis[N],sz[N],root,rootsum,sum;
void adde(int u,int v,int val){
  edge[++tot].nx=hd[u];edge[tot].to=v;edge[tot].w=val;hd[u]=tot;
  edge[++tot].nx=hd[v];edge[tot].to=u;edge[tot].w=val;hd[v]=tot;
}
void dfs(int u,int fa){
  f[++cnt]=dis[u];//找出路径
  for(int i=hd[u];i;i=edge[i].nx){
    int v=edge[i].to;
    if(v==fa||vis[v]) continue;
    dis[v]=dis[u]+edge[i].w;
    dfs(v,u);
  }
}
void findroot(int u,int fa){//求重心
  sz[u]=1;
  int maxx=0;
  for(int i=hd[u];i;i=edge[i].nx){
    int v=edge[i].to;
    if(v==fa||vis[v]) continue;
    findroot(v,u);
    sz[u]+=sz[v];
    maxx=max(maxx,sz[v]);
  }
  maxx=max(maxx,sum-sz[u]);
  if(rootsum>maxx){
    root=u;
    rootsum=maxx;
  }
}
int cul(int u,int val){//计算答案
  int res=0;
  memset(dis,0,sizeof(dis));
  cnt=0;
  dis[u]=val;
  dfs(u,0);
  sort(f+1,f+1+cnt);//排序
  int l=1,r=cnt;//尺取法?好像是这么叫的
  while(l<r){
    if(f[l]+f[r]<=k){
      res+=r-l;
      l++;
    }
    else r--;
  }
  return res;
}
void solve(int u,int fa){
  vis[u]=1;
  ans+=cul(u,0);//总情况数
  for(int i=hd[u];i;i=edge[i].nx){
    int v=edge[i].to;
    if(v==fa||vis[v]) continue;
    ans-=cul(v,edge[i].w);//不合法情况数
    sum=sz[v];
    rootsum=n;
    findroot(v,u);
    solve(root,0);
  }
}
int main(){
  n=read();
  for(int i=1;i<n;i++){
    int x=read(),y=read(),z=read();
    adde(x,y,z);
  }
  k=read();
  sum=n;
  rootsum=n;
  findroot(1,0);
  solve(root,0);
  printf("%d",ans);
  return 0;
}

Luogu P3806 【模板】点分治1

考虑像上一题类似的做法做,用一个数组 \(check\) 来表示第 \(i\) 个询问是否合法,不过这题不能由全部情况减去不符合条件的情况得到答案,所以我们需要另外判断这两点是否是在同一子树内的,思路大致和上题相同。

#include<bits/stdc++.h>
using namespace std;
inline int read(){
  int ans=0,f=1;char ch=getchar();
  while(!isdigit(ch)){if(ch=='-') f=-f;ch=getchar();}
  while(isdigit(ch)){ans=(ans<<3)+(ans<<1)+ch-48;ch=getchar();}
  return ans*f;
}
const int N=1e4+5;
int n,m;
int q[N];
int hd[N],nx[N<<1],to[N<<1],w[N<<1],tot;
int check[N];
int root,rootsum,sum;
int sz[N],vis[N];
void adde(int u,int v,int val){
  nx[++tot]=hd[u];to[tot]=v;w[tot]=val;hd[u]=tot;
  nx[++tot]=hd[v];to[tot]=u;w[tot]=val;hd[v]=tot;
}
void findroot(int u,int father){
  sz[u]=1;
  int maxx=0;
  for(int i=hd[u];i;i=nx[i]){
    int v=to[i];
    if(v==father||vis[v]) continue;
    findroot(v,u);
    sz[u]+=sz[v];
    maxx=max(maxx,sz[v]);
  }
  maxx=max(maxx,sum-sz[u]);
  if(rootsum>maxx){
    root=u;
    rootsum=maxx;
  }
}
struct QK{
  int id,val;
  bool operator<(const QK x)const{
    return val<x.val;
  }
}f[N];
int dis[N],type[N],cnt;
void dfs(int u,int father,int d,int sub){
  f[++cnt].val=d;
  f[cnt].id=u;
  type[u]=sub;
  for(int i=hd[u];i;i=nx[i]){
    int v=to[i];
    if(v==father||vis[v]) continue;
    dfs(v,u,d+w[i],sub);
  }
}
void calc(int u){
  cnt=0;
  f[++cnt].id=u;//f数组取出路径
  f[cnt].val=0;
  type[u]=u;//type数组标记属于哪个子树
  for(int i=hd[u];i;i=nx[i]){
    int v=to[i];
    if(vis[v]) continue;
    dfs(v,u,w[i],v);
  }
  sort(f+1,f+1+cnt);
  for(int i=1;i<=m;i++){
    if(check[i]) continue;
    int l=1,r=cnt;
    while(l<r){
      if(f[l].val+f[r].val>q[i]) r--;//如果大了就跳大的
      else{
	    if(f[l].val+f[r].val<q[i]) l++;//小了就跳小的,贪心
	    else
	      if(type[f[l].id]==type[f[r].id]){//在同一子树内
	        if(f[r].val==f[r-1].val) r--;
	        else l++;
	      }
	    else{check[i]=1;break;}
      }
    }
  }
}
void solve(int u){
  vis[u]=1;
  calc(u);
  for(int i=hd[u];i;i=nx[i]){
    int v=to[i];
    if(vis[v]) continue;
    rootsum=n;
    sum=sz[v];
    findroot(v,u);
    solve(root);
  }
}
int main(){
  n=read(),m=read();
  for(int i=1;i<n;i++){
    int x=read(),y=read(),z=read();
    adde(x,y,z);
  }
  for(int i=1;i<=m;i++){
    q[i]=read();
    if(!q[i]) check[i]=1;
  }
  sum=n;
  rootsum=n;
  findroot(1,0);
  solve(root);
  for(int i=1;i<=m;i++){
    if(check[i]) printf("AYE\n");
    else printf("NAY\n");
  }
  return 0;
}
posted @ 2021-04-08 11:23  Quick_Kk  阅读(101)  评论(1)    收藏  举报