BZOJ3648 寝室管理题解(基环树+点分治+树状数组)

题目:BZOJ3648.
题目大意:给定一棵 n n n个点的树或基环树,求这棵树上点个数超过 k k k的路径数量.
1 ≤ n ≤ 1 0 5 1\leq n\leq10^5 1n105.

如果这道题是树的话,这就是个点分治模板.

考虑基环树的情况.首先把环找出来,去掉环上一条边得到一棵树,对这棵树进行点分治得到一个答案,最终答案就是点分治出来的答案加上强制选择环上那条边边时基环树上点个数超过 k k k的路径数量.

考虑把经过环上某条边 ( x , y ) (x,y) (x,y)的路径 ( u , v ) (u,v) (u,v)拆成 ( u , x ) (u,x) (u,x) ( y , v ) (y,v) (y,v)两段计算贡献,此时我们发现可以枚举 v v v对应在环上的祖先,用一个树状数组维护 u u u那一段的贡献即可.

时间复杂度 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n).

代码如下:

#include<bits/stdc++.h>
using namespace std;

#define Abigail inline void
typedef long long LL;

const int N=100000;

int n,m,sk;
struct side{
  int y,next,tag;
}e[N*2+9];
int lin[N+9],cs;

void Ins(int x,int y){e[++cs].y=y;e[cs].next=lin[x];lin[x]=cs;}
void Ins2(int x,int y){Ins(x,y);Ins(y,x);}

int ca,a[N+9],vis[3][N+9];

int Dfs_a(int k){
  vis[0][k]=1;a[++ca]=k;
  for (int i=lin[k];i;i=e[i].next)
    if (!vis[0][e[i].y]) Dfs_a(e[i].y);
}

queue<int>q;
int deg[N+9];

void Topsort(){
  for (int i=1;i<=ca;++i)
    if (deg[a[i]]==1) q.push(a[i]);
  while (!q.empty()){
    int t=q.front();q.pop();
    vis[1][t]=1;
    for (int i=lin[t];i;i=e[i].next)
      if (--deg[e[i].y]==1) q.push(e[i].y);
  }
}

int rng[N+9],cr,tag[N+9];

bool Get_rng(){
  cr=0;
  for (int i=1;i<=ca;++i)
    if (!vis[1][a[i]]) {rng[cr=1]=a[i];break;}
  if (!cr) return 0;
  while (2333){
  	int flag=0;
  	tag[rng[cr]]=vis[1][rng[cr]]=1;
    for (int i=lin[rng[cr]];i;i=e[i].next)
      if (!vis[1][e[i].y]) {rng[++cr]=e[i].y;flag=1;break;}
    if (!flag) break;
  }
  return 1;
}

int siz[N+9],g[N+9];

int Dfs_siz(int k,int fa){
  siz[k]=1;g[k]=0;
  for (int i=lin[k];i;i=e[i].next)
    if (!vis[2][e[i].y]&&e[i].y^fa&&!e[i].tag){
      Dfs_siz(e[i].y,k);
	  siz[k]+=siz[e[i].y];
	  g[k]=max(g[k],siz[e[i].y]);
    }
}

int Dfs_heart(int k,int fa,int rot){
  g[k]=max(g[k],siz[rot]-siz[k]);
  int res=k,t;
  for (int i=lin[k];i;i=e[i].next)
    if (!vis[2][e[i].y]&&e[i].y^fa&&!e[i].tag){
      t=Dfs_heart(e[i].y,k,rot);
      if (g[t]<g[res]) res=t;
    }
  return res;
}

int Get_heart(int k){Dfs_siz(k,0);return Dfs_heart(k,0,k);}

LL c[N+9];

void Add(int p,LL v){for (;p<=n;p+=p&-p) c[p]+=v;}
LL Query(int p){LL res=0;for (p=p<0?0:p;p;p-=p&-p) res+=c[p];return res;}
LL Query(int L,int R){return Query(R)-Query(L-1);}

LL Dfs_calc(int k,int fa,int t,int flag){
  LL res=Query(sk-t,n);
  for (int i=lin[k];i;i=e[i].next)
    if (!(flag&&vis[2][e[i].y])&&e[i].y^fa&&!e[i].tag&&(flag||!tag[e[i].y]))
	  res+=Dfs_calc(e[i].y,k,t+1,flag);
  return res;
}

void Dfs_ins(int k,int fa,int t,int flag){
  Add(t,1);
  for (int i=lin[k];i;i=e[i].next)
    if (!(flag&&vis[2][e[i].y])&&e[i].y^fa&&!e[i].tag&&(flag||!tag[e[i].y]))
	  Dfs_ins(e[i].y,k,t+1,flag);
}

void Dfs_del(int k,int fa,int t,int flag){
  Add(t,-1);
  for (int i=lin[k];i;i=e[i].next)
    if (!(flag&&vis[2][e[i].y])&&e[i].y^fa&&!e[i].tag&&(flag||!tag[e[i].y]))
	  Dfs_del(e[i].y,k,t+1,flag);
}

LL Calc(int k){
  LL res=0;
  Add(1,1);
  for (int i=lin[k];i;i=e[i].next)
    if (!vis[2][e[i].y]&&!e[i].tag){
  	  res+=Dfs_calc(e[i].y,k,1,1);
      Dfs_ins(e[i].y,k,2,1);
    }
  Add(1,-1);
  for (int i=lin[k];i;i=e[i].next)
    if (!vis[2][e[i].y]&&!e[i].tag) Dfs_del(e[i].y,k,2,1);
  return res;
}

LL Divide(int k){
  vis[2][k=Get_heart(k)]=1;
  LL res=Calc(k);
  for (int i=lin[k];i;i=e[i].next)
    if (!vis[2][e[i].y]&&!e[i].tag) res+=Divide(e[i].y);
  return res;
}

LL Solve_rng(){
  LL res=0;
  for (int i=1;i<=cr;++i) Dfs_ins(rng[i],0,cr-i+1,0);
  for (int i=1;i<=cr;++i){
    Dfs_del(rng[i],0,cr-i+1,0);
    res+=Dfs_calc(rng[i],0,i,0);
  }
  return res;
}

LL Solve(int st){
  ca=0;
  Dfs_a(st);
  Topsort();
  if (!Get_rng()) return Divide(st);
  else{
  	for (int i=lin[rng[1]];i;i=e[i].next)
  	  if (e[i].y==rng[cr]) {e[i].tag=e[i^1].tag=1;break;}
  	return Divide(st)+Solve_rng();
  }
}

LL ans;

Abigail into(){
  scanf("%d%d%d",&n,&m,&sk);
  cs=1;
  for (int i=1;i<=m;++i){
  	int x,y;
	scanf("%d%d",&x,&y);
	Ins2(x,y);
	++deg[x];++deg[y];
  }
}

Abigail work(){
  for (int i=1;i<=n;++i)
    if (!vis[0][i]) ans+=Solve(i);
}

Abigail outo(){
  printf("%lld\n",ans);
}

int main(){
  into();
  work();
  outo();
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值