[BZOJ3772]精神污染(dfs序+主席树)

题目描述

传送门

题解

对于每一条链计算能完全覆盖它的有多少条。
处理出来dfs序了之后,可以发现边大概分为三种情况:x和y的lca不是x和y中的某一个,x和y的lca是x或y,还有就是一条路径就是一个点。对于第一种情况,能覆盖它的路径一定是一个端点在x的子树里,一个端点在y的子树里。对于第二种情况,能覆盖它的路径一定是一个端点在y的子树里,另外一个端点在x的子树外。对于第三种情况,除了计算和第二种情况相似的之外,还要计算有多少条路径的lca是这个点,也就是覆盖了这个点。
用权值线段树套权值线段树即可,内层用动态开点,那么空间和时间都是 O(nlogn) 。外层表示路径端点较小的dfs序,内层表示路径端点较大的dfs序。

刚开始竟然手残把lca写错了,gg。

代码

#include
#include
#include
#include
using namespace std;
#define LL long long
#define N 100005
#define sz 17

int n,m,x,y,dfs_clock,last,size,rest;
struct hp{int x,y,r,last;}e[N];
int tot,point[N],nxt[N*2],v[N*2];
int h[N],in[N],out[N],root[N],f[N][sz+3],cnt[N];
LL sum[N*20];int ls[N*20],rs[N*20];
LL up,down;

int cmp(hp a,hp b)
{
    return in[a.x]<in[b.x]||(in[a.x]==in[b.x]&&in[a.y]<in[b.y]);
}
void add(int x,int y)
{
    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;
}
void dfs(int x,int fa)
{
    in[x]=++dfs_clock;h[x]=h[fa]+1;
    for (int i=1;iif (h[x]-(1<1) break;
        f[x][i]=f[f[x][i-1]][i-1];
    }
    for (int i=point[x];i;i=nxt[i])
        if (v[i]!=fa)
        {
            f[v[i]][0]=x;
            dfs(v[i],x);
        }
    out[x]=dfs_clock;
}
int lca(int x,int y)
{
    if (x==y) {last=x;return x;}
    if (h[x]for (int i=sz-1;i>=0;--i)
        while (h[f[x][i]]>h[y])
            x=f[x][i];
    last=x;
    if (h[x]>h[y]) x=f[x][0];
    if (x==y) return x;
    for (int i=sz;i>=0;--i)
        if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    return f[x][0];
}
void update(int &now,int l,int r,int x)
{
    int mid=(l+r)>>1;
    sum[++size]=sum[now]+1,ls[size]=ls[now],rs[size]=rs[now];now=size;
    if (l==r) return;
    if (x<=mid) update(ls[now],l,mid,x);
    else update(rs[now],mid+1,r,x);
}
LL query(int now,int l,int r,int lrange,int rrange)
{
    int mid=(l+r)>>1;
    LL ans=0;
    if (lrange<=l&&r<=rrange) return sum[now];
    if (lrange<=mid) ans+=query(ls[now],l,mid,lrange,rrange);
    if (mid+1<=rrange) ans+=query(rs[now],mid+1,r,lrange,rrange);
    return ans;
}
LL gcd(LL a,LL b)
{
    if (!b) return a;
    else return gcd(b,a%b);
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i"%d%d",&x,&y);
        add(x,y);add(y,x);
    }
    dfs(1,0);
    for (int i=1;i<=m;++i)
    {
        scanf("%d%d",&x,&y);
        if (in[x]>in[y]) swap(x,y);
        e[i].x=x,e[i].y=y;e[i].r=lca(x,y);cnt[e[i].r]++;
        e[i].last=last;
    }
    sort(e+1,e+m+1,cmp);
    for (int i=1;i<=m;++i)
    {
        if (i!=1&&in[e[i-1].x]!=in[e[i].x]-1)
            for (int j=in[e[i-1].x]+1;j<in[e[i].x];++j) root[j]=root[j-1];
        root[in[e[i].x]]=root[in[e[i-1].x]];
        update(root[in[e[i].x]],1,n,in[e[i].y]);
    }
    for (int i=in[e[m].x]+1;i<=n;++i) root[i]=root[i-1];
    for (int i=1;i<=m;++i)
    {
        int r=e[i].r;last=e[i].last;
        if (r==e[i].x)
        {   
            if (e[i].x!=e[i].y)
            {
                int a=1,b=in[last]-1,c=in[e[i].y],d=out[e[i].y];
                if (a<=b)
                    up+=query(root[b],1,n,c,d)-query(root[a-1],1,n,c,d);
                a=in[e[i].y],b=out[e[i].y],c=out[last]+1;d=n;
                if (c<=d)
                    up+=query(root[b],1,n,c,d)-query(root[a-1],1,n,c,d);
            }
            else
            {
                int a=1,b=in[last]-1,c=in[last],d=out[last];
                if (a<=b)
                    up+=query(root[b],1,n,c,d)-query(root[a-1],1,n,c,d);
                a=in[last],b=out[last],c=out[last]+1,d=n;
                if (c<=d)
                    up+=query(root[b],1,n,c,d)-query(root[a-1],1,n,c,d);
                up+=(LL)cnt[last];
            }
        }
        else
        {
            int a=in[e[i].x],b=out[e[i].x],c=in[e[i].y],d=out[e[i].y];
            up+=query(root[b],1,n,c,d)-query(root[a-1],1,n,c,d);
        }
    }
    up-=m;down=(LL)m*((LL)m-1)/2;
    LL t=gcd(up,down);up/=t,down/=t;
    printf("%lld/%lld\n",up,down);
    return 0;
}

总结

一定要注意写主席树的时候要保证每一个点都有根(即使是光继承上一个),否则的话很可能减出负数来。

你可能感兴趣的:(题解,可持久化,dfs序)