(倍增LCA+树上差分)POJ 3417:【例 2】暗的连锁

题意:先给出一棵无根树,然后下面再给出m条边,把这m条边连上,然后每次你能毁掉两条边,规定一条是树边,一条是新边,问有多少种方案能使树断裂。

STEP ONE:

我们考虑所有的新边:

对于新边(u,v),必然会和树边构成一个环:u-->lca(u,v)-->v--->u

如果只有一个环,显然,我们只需要破坏任意一条这条路径上的树边,以及这条新边即可

我们考虑对所有的树边维护:有多少个环覆盖了当前树边,即为cnt[x]

如果cnt[x]>=2,有至少两个环覆盖它,显然,我们的一次操作最多只能破坏一个环,无论如何都无法断开,贡献为0

cnt[x]==1 只需要破坏当前树边和对应的构成环的新边即可,贡献为1

cnt[x]==0 仅仅破坏这个树边就可以使树断裂,可以搭配上任意一个新边,贡献为m(任取一条新边都行)

STEP TWO:

我们需要记录任何一条树边经过了几次:

显然,我们可以用树上边差分来统计答案!

对于(u,v)路径,cnt[u]++,cnt[v]++,cnt[lca(u,v)]-=2;

从根节点统计差分情况即可!

#include<cstdio>
#include<vector>
using namespace std;
const int N=100020,logN=18;
vector <int> map[N];
struct node{
int to;
int next;
int val;
}e[N<<1];
int head[N],ans,num,n,m,log[N],f[N][logN],dep[N],cnt[N];
void addedge(int from,int to)
{
e[++num].to=to;
e[num].next=head[from];
head[from]=num;
}
void dfs(int u,int fa)
{
dep[u]=dep[fa]+1;
f[u][0]=fa;
int v;
for(int i=1;i<=log[dep[u]];i++)
f[u][i]=f[f[u][i-1]][i-1];
for(int i=head[u];i;i=e[i].next)
{
v=e[i].to;
if(v==fa)
continue;
dfs(v,u);
}
}
void swap(int &a,int &b)
{
int c=a;
a=b;
b=c;
}
int ask(int x,int y)
{
if(dep[x]<dep[y])
swap(x,y);
while(dep[x]>dep[y])
x=f[x][log[dep[x]-dep[y]]];
if(x==y)
return x;
for(int i=log[dep[x]];i>=0;i--)
{
if(f[x][i]!=f[y][i])
{
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
void calc(int u,int fa)
{
int v;
for(int i=head[u];i;i=e[i].next)
{
v=e[i].to;
if(v==fa)
continue;
calc(v,u);
cnt[u]+=cnt[v];
}
}
int main()
{
int x,y;
scanf("%d%d",&n,&m);
for(int i=1;i<=n-1;i++)//read build graph
{
scanf("%d%d",&x,&y);
addedge(x,y);
addedge(y,x);
}
log[0]=-1;//LCA
for(int i=1;i<=n;i++)
log[i]=log[i>>1]+1;
dfs(1,0);

for(int i=1;i<=m;i++)
{
//对于每一次读入,直接差分,不需要存储新边!
scanf("%d%d",&x,&y);
int LCA=ask(x,y);
//树上差分
cnt[x]++;
cnt[y]++;
cnt[LCA]-=2;
}
calc(1,0);//统计差分答案
for(int i=2;i<=n;i++)
{
if(cnt[i]==0) ans+=m;
if(cnt[i]==1) ans++;
}
printf("%d",ans);
return 0;
}

猜你喜欢

转载自www.cnblogs.com/little-cute-hjr/p/11866709.html