bzoj3772 精神污染(dfs序+主席树)

Description

兵库县位于日本列岛的中央位置,北临日本海,南面濑户内海直通太平洋,中央部位是森林和山地,与拥有关西机场的大阪府比邻而居,是关西地区面积最大的县,是集经济和文化于一体的一大地区,是日本西部门户,海陆空交通设施发达。濑户内海沿岸气候温暖,多晴天,有日本少见的贸易良港神户港所在的神户市和曾是豪族城邑“城下町”的姬路市等大城市,还有以疗养地而闻名的六甲山地等。
兵库县官方也大力发展旅游,为了方便,他们在县内的N个旅游景点上建立了n-1条观光道,构成了一棵图论中的树。同时他们推出了M条观光线路,每条线路由两个节点x和y指定,经过的旅游景点就是树上x到y的唯一路径上的点。保证一条路径只出现一次。
你和你的朋友打算前往兵库县旅游,但旅行社还没有告知你们最终选择的观光线路是哪一条(假设是线路A)。这时候你得到了一个消息:在兵库北有一群丧心病狂的香菜蜜,他们已经选定了一条观光线路(假设是线路B),对这条路线上的所有景点都释放了【精神污染】。这个计划还有可能影响其他的线路,比如有四个景点1-2-3-4,而【精神污染】的路径是1-4,那么1-3,2-4,1-2等路径也被视为被完全污染了。
现在你想知道的是,假设随便选择两条不同的路径A和B,存在一条路径使得如果这条路径被污染,另一条路径也被污染的概率。换句话说,一条路径被另一条路径包含的概率。

Input

第一行两个整数N,M
接下来N-1行,每行两个数a,b,表示A和B之间有一条观光道。
接下来M行,每行两个数x,y,表示一条旅游线路。

Output

所求的概率,以最简分数形式输出。

Sample Input

5 3

1 2

2 3

3 4

2 5

3 5

2 5

1 4

Sample Output

1/3

样例解释

可以选择的路径对有(1,2),(1,3),(2,3),只有路径1完全覆盖路径2。

HINT

100%的数据满足:N,M<=100000

Source

[ Submit][ Status][ Discuss]



分析:
%PoPoQQQ
看到网上大多的题解都来自PoPoQQQdada的blog,所以也%一发
然而dalao的题解都比较神,不是很明白

首先:如果路径A包含于路径B,那么就有A的两端点在路径B上,这是个充要条件
那么问题转化为了对于一条路径,判断两个端点都在这条路径上的路径有多少条
对于每一个节点x,我们用一个vector来存结点y,当且仅当存在一条路径x–>y

主席树相当于前缀和S

处理出来dfs序了之后,可以发现边大概分为三种情况
(我们令x为两个点中dfs序较小的点)

  • x和y的lca不是x和y中的某一个,那么所有能覆盖ta的路径就是一个端点在x的子树中,另一个在y的子树中
  • x和y的lca是x或y,能覆盖ta的路径一定是一个端点在y的子树中,另一个在x的子树外
  • 一条路径就是一个点,和第二种情况一样处理,其中路径的lca是该点的数目特殊计算
用权值线段树套权值线段树,内层用动态开点

空间和时间都是 O(nlogn) O ( n l o g n )
外层表示路径端点较小的dfs序,内层表示路径端点较大的dfs序


我能找到的题解最多的也就是上面几句话
所以还是根据代码仔细说一下:

首先建出树来,dfs一遍

得到dfs序以及每个结点代表的子树的出栈入栈位置
读入路径,并且按照dfs序排序

if (in[e[i].x]>in[e[i].y]) swap(e[i].x,e[i].y);   //保证x是dfs序中靠前的一个

int cmp(const point &A,const point &B) 
{return in[A.x]<in[B.x]||in[A.x]==in[B.x]&&in[A.y]<in[B.y];} 
之后就是主席树的构建

我们要按照dfs序insert,但是题目给出的路径不一定包含所有的结点
但是我们要保证:每一个点都有根(即使是光继承上一个)

int now=1;
for (int i=1;i<=m;i++)
{
    while (now<=clo&&now<in[e[i].x]) root[now+1]=root[now],now++;
    insert(root[now],1,clo,in[e[i].y]);    //插入y的入栈位置 
}
for (int i=now+1;i<=clo;i++) root[i]=root[i-1]; 

(一开始在维护root时候,我的写法是:root[i]=root[i-1],但是这样写是错的)
在一条路径起点代表的权值线段树中,插入路径终点的入栈位置
这样按照一定顺序插入,在记录答案的时候
对于路径x只询问编号比x小的可行路径数,就可以不重不漏了

现在我们就可以计算答案了
if (e[i].x==e[i-1].x&&e[i].y==e[i-1].y) ans--;   //自己对自己的影响不计入答案
情况一: lca(x,y)!=x,lca(x,y)!=y l c a ( x , y ) ! = x , l c a ( x , y ) ! = y

这样的路径一端在x的子树中,一端在y的子树中
主席树是以dfs为基础建造的,里面记录的都是路径的终点
我们可以找到此路径起点x代表的线段树
询问在起点子树x内 root[in[e[i].x]1],root[out[e[i].x]] r o o t [ i n [ e [ i ] . x ] − 1 ] , r o o t [ o u t [ e [ i ] . x ] ]
终点在子树y内 in[e[i].y],out[e[i].y] i n [ e [ i ] . y ] , o u t [ e [ i ] . y ]
的路径条数
(实际上就是区间 [in[e[i].y],out[e[i].y]] [ i n [ e [ i ] . y ] , o u t [ e [ i ] . y ] ] 内的sum)

int p=lca(e[i].x,e[i].y);
if (p!=e[i].x&&p!=e[i].y)
{
    val[p]++;
    ans+=(ll)ask(root[in[e[i].x]-1],root[out[e[i].x]],1,clo,in[e[i].y],out[e[i].y]);
    ans--;                                       //自己对自己的影响不计入答案
} 
情况二: lca(x,y)=x||y l c a ( x , y ) = x | | y

这样的路径一端在y的子树中,一端在x的子树外
其中,X是lca的儿子
在dfs序中,非x子树分成了两部分: [0,in[X]1],[out[X]+1,n] [ 0 , i n [ X ] − 1 ] , [ o u t [ X ] + 1 , n ]
但是我们不能无脑的询问:

ask(root[0],root[in[X]-1],in[e[i].y],out[e[i].y])
ask(root[out[X]+1],root[n],in[e[i].y],out[e[i].y])

为什么?
因为主席树的建立是按照dfs序的,如果询问 (root[out[X]+1],root[n]) ( r o o t [ o u t [ X ] + 1 ] , r o o t [ n ] )
就不可能“对于路径x只询问编号比x小的可行路径数,不重不漏
因此我们要询问:

ask(root[0],root[in[X]-1],in[e[i].y],out[e[i].y])
ask(root[in[e[i].y]-1],root[out[e[i].y]],out[X],n)
if ((p==e[i].x||p==e[i].y)&&(e[i].x!=e[i].y))
{
    int L=in[e[i].x],R=out[e[i].x];
    int t1=in[X],t2=out[X];
    ans+=(ll)ask(root[0],root[t1-1],1,clo,in[e[i].y],out[e[i].y]);
    L=in[e[i].y],R=out[e[i].y];
    if (t2+1<=clo)
        ans+=(ll)ask(root[L-1],root[R],1,clo,t2+1,clo);
    ans--;
}
情况二:单点路径

像情况二一样处理

int L=in[e[i].x],R=out[e[i].x];
ans+=(ll)ask(root[0],root[L],1,clo,in[e[i].y],out[e[i].y]);
if (R+1<=clo) ans+=(ll)ask(root[L-1],root[R],1,clo,R+1,clo);
ans--;
最后

对于单点路径,我们之前只计算了两端点不在一个子树内的路径数
这里还要计算折线路径的个数
val v a l lca l c a e[i].x e [ i ] . x 的路径条数

for (int i=1;i<=m;i++)    //处理单点的路径 
{
    int p=lca(e[i].x,e[i].y);
    if (e[i].x==e[i].y) ans+=(ll)val[e[i].x];  
}

tip

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

注意:自身产生的影响不计入答案(每一种情况都要考虑这点)
#include<bits/stdc++.h>
#define ll long long

using namespace std;

const int N=100010;
struct point{
    int x,y;
}e[N];
struct node{
    int y,nxt;
}way[N<<1];
struct Tree{
    int l,r,sum;
}t[N*20];
int n,m,st[N],tot=0,lg,top=0,X,root[N],val[N];
int clo=0,dfn[N],deep[N],pre[N][20],in[N],out[N];

void add(int x,int y)
{
    tot++;way[tot].y=y;way[tot].nxt=st[x];st[x]=tot;
    tot++;way[tot].y=x;way[tot].nxt=st[y];st[y]=tot;
}

int cmp(const point &A,const point &B) {return in[A.x]<in[B.x]||in[A.x]==in[B.x]&&in[A.y]<in[B.y];} 

ll gcd(ll a,ll b)
{
    ll r=a%b;
    while (r)
    {
        a=b; b=r;
        r=a%b;
    }
    return b;
} 

void dfs(int now,int fa,int dep)
{
    deep[now]=dep;
    pre[now][0]=fa;
    for (int i=1;i<=lg;i++)
    {
        if (deep[now]-(1<<i)<0) break;
        pre[now][i]=pre[pre[now][i-1]][i-1];
    }
    dfn[++clo]=now; in[now]=clo;
    for (int i=st[now];i;i=way[i].nxt)
        if (way[i].y!=fa)
            dfs(way[i].y,now,dep+1);
    out[now]=clo;
}

void insert(int &now,int l,int r,int x)
{
    top++;
    t[top]=t[now];
    now=top;
    t[now].sum++;
    if (l==r) return;
    int mid=(l+r)>>1;
    if (x<=mid) insert(t[now].l,l,mid,x);
    else insert(t[now].r,mid+1,r,x);
}

int ask(int x,int y,int l,int r,int L,int R)
{
    if (l>=L&&r<=R) return t[y].sum-t[x].sum;
    int mid=(l+r)>>1;
    int ans=0;
    if (L<=mid) ans+=ask(t[x].l,t[y].l,l,mid,L,R);
    if (R>mid) ans+=ask(t[x].r,t[y].r,mid+1,r,L,R);
    return ans;
}

int lca(int x,int y)
{
    if (deep[x]<deep[y]) swap(x,y);
    int d=deep[x]-deep[y]-1;
    if (d)
        for (int i=0;i<=lg&&d;i++,d>>=1)
            if (d&1)
                x=pre[x][i];
    X=x;
    x=pre[x][0];
    if (x==y) return x;
    for (int i=lg;i>=0;i--)
        if (pre[x][i]!=pre[y][i])
            x=pre[x][i],y=pre[y][i];
    X=x;
    return pre[x][0];
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y);
    }

    lg=log(n)/log(2);
    dfs(1,0,1);

    for (int i=1;i<=m;i++)
    {
        scanf("%d%d",&e[i].x,&e[i].y);
        if (in[e[i].x]>in[e[i].y]) swap(e[i].x,e[i].y);   //保证x是dfs序中靠前的一个 
    }

    sort(e+1,e+1+m,cmp);    //按照dfs序插入 
    int now=1;
    for (int i=1;i<=m;i++)
    {
        while (now<=clo&&now<in[e[i].x]) root[now+1]=root[now],now++;
        insert(root[now],1,clo,in[e[i].y]);    //插入y的入栈位置 
    }
    for (int i=now+1;i<=clo;i++) root[i]=root[i-1]; 

    ll ans=0;
    for (int i=1;i<=m;i++)
    {
        if (e[i].x==e[i-1].x&&e[i].y==e[i-1].y) ans--;   //自己对自己的影响不计入答案
        int p=lca(e[i].x,e[i].y);
        if (p!=e[i].x&&p!=e[i].y)
        {
            val[p]++;
            ans+=(ll)ask(root[in[e[i].x]-1],root[out[e[i].x]],1,clo,in[e[i].y],out[e[i].y]);
            ans--;                                       //自己对自己的影响不计入答案
        } 
        else if ((p==e[i].x||p==e[i].y)&&(e[i].x!=e[i].y))
        {
            int L=in[e[i].x],R=out[e[i].x];
            int t1=in[X],t2=out[X];
            ans+=(ll)ask(root[0],root[t1-1],1,clo,in[e[i].y],out[e[i].y]);
            L=in[e[i].y],R=out[e[i].y];
            if (t2+1<=clo)
                ans+=(ll)ask(root[L-1],root[R],1,clo,t2+1,clo);
            ans--;
        }
        else
        {
            int L=in[e[i].x],R=out[e[i].x];
            ans+=(ll)ask(root[0],root[L],1,clo,in[e[i].y],out[e[i].y]);
            if (R+1<=clo) ans+=(ll)ask(root[L-1],root[R],1,clo,R+1,clo);
            ans--;
        }
    }

    for (int i=1;i<=m;i++)    //处理单点的路径 
    {
        int p=lca(e[i].x,e[i].y);
        if (e[i].x==e[i].y) ans+=(ll)val[e[i].x];  
    }
    ans=max(ans,0LL);
    ll tt=(ll)m*(m-1)/2;
    ll r=gcd(ans,tt);
    printf("%lld/%lld",ans/r,tt/r); 

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值