JZOJ5394. 【NOIP2017提高A组模拟10.5】Ping 树上差分 树状数组

21 篇文章 0 订阅
11 篇文章 0 订阅

题意:有一棵树,给你一些点对,要求这些点对不能联通,问最少需要拆掉多少个点。输出方案。
比赛的时候有想法的一道题目,但是由于比较煞笔所以只想到了树剖,然后觉得万一不能切就亏大了,求稳所以没打。
考虑一对点,他们的路径为x-lca-y,那么对于这条路径上查询是否有被拆掉的点,没有就直接把lca拆掉。
那么我们对于所有点对都这么干,按照dfs序倒着处理,证明的话,把树看成一个序列,会发现按照右端点排序贪心最优,延伸到树上也是一样的道理。
维护路径点对数量,根据bit前缀和性质,我们可以在区间左边打一个+1,右边打一个-1标记。
注意,如果剖分的话才可以直接对dfn[x],dfn[y]打标记,不然的话就要记录每个点子树的dfn区间,剖分才能把点化成线段(有序),就是pos[dfn[x]],pos[dfn[y]]。
各有千秋吧。

#include<cstdio>
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=3e5+5;
int n,m,k;
int a[N];
int dfn[N],l[N],r[N],head[N],next[N],go[N];
int fa[N][20],dep[N],size[N],pos[N];
bool vis[N];
int t[N],tot;
struct node
{
    int x,y,lca;
}b[N];
inline void addedge(int x,int y)
{
    go[++tot]=y;
    next[tot]=head[x];
    head[x]=tot;
}
int tim;
inline int lowbit(int x)
{
    return x&(-x);
}
inline void add(int x,int y)
{
    while (x<=n)
    {
        t[x]+=y;
        x+=lowbit(x);
    }
}
inline int ask(int x)
{
    int ret=0;
    while (x>0)
    {
        ret+=t[x];
        x-=lowbit(x);
    }
    return ret;
}
inline void dfs(int x)
{
    dfn[x]=++tim;
    dep[x]=dep[fa[x][0]]+1;
    l[x]=tim;
    fo(i,1,18)fa[x][i]=fa[fa[x][i-1]][i-1];
    for(int i=head[x];i;i=next[i])
    {
        int v=go[i];
        if (v!=fa[x][0])
        {
            fa[v][0]=x;
            dfs(v);
        }
    }
    r[x]=tim;
}
bool cmp(node x,node y)
{
    return dfn[x.lca]>dfn[y.lca];
}
inline int lca(int x,int y)
{
    if (dep[x]<dep[y])swap(x,y);
    fd(i,18,0)
    if (dep[fa[x][i]]>=dep[y])x=fa[x][i];
    if (x==y)return x;
    fd(i,18,0)
    if (fa[x][i]!=fa[y][i])
    {
        x=fa[x][i];
        y=fa[y][i];
    }
    return fa[x][0];
}
int main()
{
    //freopen("ping.in","r",stdin);
    //freopen("ping.out","w",stdout);
    scanf("%d%d",&n,&m);
    fo(i,1,m)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        addedge(x,y),addedge(y,x);
    }
    dfs(1);
    scanf("%d",&k);
    int ans=0,tot=0;
    fo(i,1,k)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        int Lca=lca(x,y);
        b[i].x=x,b[i].y=y,b[i].lca=Lca;
    }
    sort(b+1,b+1+k,cmp);
    fo(i,1,k)
    {
            int x=b[i].x,y=b[i].y,Lca=b[i].lca;
            int tmp1=ask(dfn[x]);
            int tmp2=ask(dfn[y]);
            int tmp3=ask(dfn[Lca]);
            int tmp4=ask(dfn[fa[Lca][0]]);
            if(tmp1+tmp2-tmp3-tmp4<=0)
            {
                ans++,vis[Lca]=1;
                add(dfn[Lca],1);
                add(r[Lca]+1,-1);
            }
    }
    printf("%d\n",ans);
    fo(i,1,n)if (vis[i])printf("%d ",i);
    printf("\n");
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值