Description

无向图中有N个节点和两类边,一类边被称为主要边,而另一类被称为附加边。Dark有N – 1条主要边,并且Dark的任意两个节点之间都存在一条只由主要边构成的路径。另外,Dark还有M条附加边。
你的任务是把Dark斩为不连通的两部分。一开始Dark的附加边都处于无敌状态,你只能选择一条主要边切断。一旦你切断了一条主要边,Dark就会进入防御模式,主要边会变为无敌的而附加边可以被切断。但是你的能力只能再切断Dark的一条附加边。现在你想要知道,一共有多少种方案可以击败Dark。注意,就算你第一步切断主要边之后就已经把Dark斩为两截,你也需要切断一条附加边才算击败了Dark。

Input

第一行包含两个整数N和M。
之后N – 1行,每行包括两个整数A和B,表示A和B之间有一条主要边。
之后M行以同样的格式给出附加边。

Output

输出一个整数表示答案。

Sample Input

4 1
1 2
2 3
1 4
3 4

Sample Output

3

Hint

对于20% 的数据,N≤100,M≤100。
对于100% 的数据,N≤100 000,M≤200 000。数据保证答案不超过2^31– 1。

题解:

对于每一条主要边,讨论被几条附加边覆盖

若没有附加边覆盖则去掉这条主要边后,DARK已经成了不连通的两部分,则只需切掉任意一条附加边,对答案贡献m

若被一条附加边覆盖,则去掉条主要边后必须去掉这条附加边,对答案贡献1

若被大于一条附加变覆盖,这不能被切成不连通的两部分

可以用每一个点代表与这个点的dad相连的边

做法一:树链剖分+线段树

每加入一条附加边时把两点之间的路径上的所有点在线段树中加上一,最后做n遍单点查询。

代码:

#include<iostream>

#include<cstdio>

#include<cstring>

#include<cstdlib>

#include<algorithm>

#include<cmath>

#include<queue>

#include<cctype>

#include<cfloat>

#include<vector>

#include<map>

#define ls o*2

#define rs o*2+1

#define mi int mid=(l+r)>>1

#define maxn 100010

using namespace std;

struct data{

  int nex,to;

}e[maxn*2];

int head[maxn],edge=0;

int top[maxn],deep[maxn],son[maxn],dad[maxn],size[maxn],sp[maxn];

bool co[maxn];int b[maxn*4],lazy[maxn*4],siz[4*maxn],n;

void build(int o,int l,int r)

{

  siz[o]=r-l+1;

  if(l==r) return;

  mi;

  build(ls,l,mid);

  build(rs,mid+1,r);

}

void add(int from,int to)

{

  e[++edge].nex=head[from];

  e[edge].to=to;

  head[from]=edge;

}

void dfs1(int x)

{

  if(co[x]) return;

  co[x]=1;

  size[x]=1;

  for(int i=head[x];i;i=e[i].nex)

    {

      int u=e[i].to;

      if(co[u])continue;

      dad[u]=x;

      deep[u]=deep[x]+1;

      dfs1(u);

      size[x]+=size[u];

      if(size[son[x]]<size[u]) son[x]=u;

    }

}

int de=0;

void dfs2(int x)

{

  if(co[x] || x==0) return;

  co[x]=1;

  sp[x]=++de;

  top[son[x]]=top[x];

  dfs2(son[x]);

  for(int i=head[x];i;i=e[i].nex)

    {

      int u=e[i].to;

      if(co[u] || u==son[x]) continue;

      top[u]=u;

      dfs2(u);

    }

}

void down(int o)

{

  b[ls]+=lazy[o]*siz[ls],b[rs]+=lazy[o]*siz[rs];

  lazy[ls]+=lazy[o],lazy[rs]+=lazy[o];

  lazy[o]=0;

}

void change(int o,int l,int r,int u,int v)

{

  if(l!=r) down(o);

  if(u>v) return;

  if(l>=u&&r<=v) {b[o]+=siz[o];lazy[o]+=1;return;}

  if(l>v || r<u) return;mi;

  if(v<=mid) change(ls,l,mid,u,v);

  else if(u>mid) change(rs,mid+1,r,u,v);

  else change(ls,l,mid,u,mid),change(rs,mid+1,r,mid+1,v);

}

void TREE_CUT(int x,int y)

{

  while(top[x]!=top[y]){

    if(deep[top[x]]>deep[top[y]]) change(1,1,n,sp[top[x]],sp[x]),x=dad[top[x]];

    else change(1,1,n,sp[top[y]],sp[y]),y=dad[top[y]];

  }

  if(deep[x]>deep[y]) swap(x,y);

  change(1,1,n,sp[x]+1,sp[y]);

}

int find(int o,int l,int r,int p)

{

  if(l!=r) down(o);

  if(l==r) return b[o];

  mi;

  if(p<=mid) return find(ls,l,mid,p);

  else return find(rs,mid+1,r,p);

}

int main()

{

  //freopen("!.in","r",stdin);

  //freopen("!.out","w",stdout);

  int m,x,y;

  scanf("%d%d",&n,&m);

  build(1,1,n);

  for(int i=1;i<n;i++)

    scanf("%d%d",&x,&y),add(x,y),add(y,x);

  top[1]=1,deep[1]=1,dad[1]=1;

  dfs1(1);memset(co,0,sizeof(co));dfs2(1);

  for(int i=1;i<=m;i++)

    scanf("%d%d",&x,&y),TREE_CUT(x,y);

  int ans=0;

  for(int i=2;i<=n;i++)

    {

      int op=find(1,1,n,sp[i]);

      if(op==1) ans+=1;

      if(op==0) ans+=m;

    }

  printf("%d\n",ans);

  return 0;

}

做法二:树链剖分+树上差分

注意到只有区间修改和单点查询,这很容易想到差分,只要将树剖成链,然后就和链上的差分一样的了。

代码:

#include<iostream>

#include<cstdio>

#include<cstring>

#include<cstdlib>

#include<algorithm>

#include<cmath>

#include<queue>

#include<cctype>

#include<cfloat>

#include<vector>

#include<map>

#define ls o*2

#define rs o*2+1

#define mi int mid=(l+r)>>1

#define maxn 100010

using namespace std;

struct data{

  int nex,to;

}e[maxn*2];

int head[maxn],edge=0;

int top[maxn],deep[maxn],son[maxn],dad[maxn],size[maxn],sp[maxn];

bool co[maxn];int b[maxn*4],lazy[maxn*4],siz[4*maxn],n,cha[maxn];

void add(int from,int to)

{

  e[++edge].nex=head[from];

  e[edge].to=to;

  head[from]=edge;

}

void dfs1(int x)

{

  if(co[x]) return;

  co[x]=1;

  size[x]=1;

  for(int i=head[x];i;i=e[i].nex)

    {

      int u=e[i].to;

      if(co[u])continue;

      dad[u]=x;

      deep[u]=deep[x]+1;

      dfs1(u);

      size[x]+=size[u];

      if(size[son[x]]<size[u]) son[x]=u;

    }

}

int de=0;

void dfs2(int x)

{

  if(co[x] || x==0) return;

  co[x]=1;

  sp[x]=++de;

  top[son[x]]=top[x];

  dfs2(son[x]);

  for(int i=head[x];i;i=e[i].nex)

    {

      int u=e[i].to;

      if(co[u] || u==son[x]) continue;

      top[u]=u;

      dfs2(u);

    }

}

void change(int x,int y)

{

  cha[x]++,cha[y+1]--;

}

void TREE_CUT(int x,int y)

{

  while(top[x]!=top[y]){

    if(deep[top[x]]>deep[top[y]]) change(sp[top[x]],sp[x]),x=dad[top[x]];

    else change(sp[top[y]],sp[y]),y=dad[top[y]];

  }

  if(deep[x]>deep[y]) swap(x,y);

  change(sp[x]+1,sp[y]);

}

int main()

{

 // freopen("!.in","r",stdin);

 // freopen("!.out","w",stdout);

  int m,x,y;

  scanf("%d%d",&n,&m);

  for(int i=1;i<n;i++)

    scanf("%d%d",&x,&y),add(x,y),add(y,x);

  top[1]=1,deep[1]=1,dad[1]=1;

  dfs1(1);memset(co,0,sizeof(co));dfs2(1);

  for(int i=1;i<=m;i++)

    scanf("%d%d",&x,&y),TREE_CUT(x,y);

  int ans=0;

  cha[1]=0;

  for(int i=2;i<=n;i++)

    {

      cha[i]+=cha[i-1];

      int op=cha[i];

      if(op==1) ans+=1;

      if(op==0) ans+=m;

    }

  printf("%d\n",ans);

  return 0;

}
05-07 15:55