支配树:

一、有向无环图:

例①:P2597 [ZJOI2012]灾难:

题目描述
阿米巴是小强的好朋友。

阿米巴和小强在草原上捉蚂蚱。小强突然想,如果蚂蚱被他们捉灭绝了,那么吃蚂蚱的小鸟就会饿死,而捕食小鸟的猛禽也会跟着灭绝,从而引发一系列的生态灾难。

学过生物的阿米巴告诉小强,草原是一个极其稳定的生态系统。如果蚂蚱灭绝了,小鸟照样可以吃别的虫子,所以一个物种的灭绝并不一定会引发重大的灾难。

我们现在从专业一点的角度来看这个问题。我们用一种叫做食物网的有向图来描述生物之间的关系:
在这里插入图片描述
一个食物网有N个点,代表N种生物,如果生物x可以吃生物y,那么从y向x连一个有向边。

这个图没有环。

图中有一些点没有连出边,这些点代表的生物都是生产者,可以通过光合作用来生存; 而有连出边的点代表的都是消费者,它们必须通过吃其他生物来生存。

如果某个消费者的所有食物都灭绝了,它会跟着灭绝。

我们定义一个生物在食物网中的“灾难值”为,如果它突然灭绝,那么会跟着一起灭绝的生物的种数。

举个例子:在一个草场上,生物之间的关系是:

如果小强和阿米巴把草原上所有的羊都给吓死了,那么狼会因为没有食物而灭绝,而小强和阿米巴可以通过吃牛、牛可以通过吃草来生存下去。所以,羊的灾难值是1。但是,如果草突然灭绝,那么整个草原上的5种生物都无法幸免,所以,草的灾难值是4。

给定一个食物网,你要求出每个生物的灾难值。

输入格式
输入文件 catas.in 的第一行是一个正整数 N,表示生物的种数。生物从 1 标

号到 N。

接下来 N 行,每行描述了一个生物可以吃的其他生物的列表,格式为用空

格隔开的若干个数字,每个数字表示一种生物的标号,最后一个数字是 0 表示列

表的结束。

输出格式
输出文件catas.out包含N行,每行一个整数,表示每个生物的灾难值。

输入输出样例
输入 #1 复制
5
0
1 0
1 0
2 3 0
2 0
输出 #1 复制
4
1
0
0
0
说明/提示
【样例说明】

样例输入描述了题目描述中举的例子。

【数据规模】

对50%的数据,N ≤ 10000。

对100%的数据,1 ≤ N ≤ 65534。

输入文件的大小不超过1M。保证输入的食物网没有环。

思路:求有向无环图每个点可以支配的的点数,不包括其自身。就是求支配树上每个点为根的子树的大小。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<set>
#include<deque>
#include<map>
#include<vector>
#include<cmath>
#define ll long long
#define llu unsigned ll
#define pr pair<int,int>
using namespace std;
const ll lnf=0x3f3f3f3f3f3f3f3f;
const int inf = 0x3f3f3f3f;
const int maxn=2000100;
const int mod=1e9+7;
int head[maxn],ver[maxn],nt[maxn];
int hh[maxn],vv[maxn],nn[maxn];
int d[maxn],f[maxn][22],si[maxn],t,n;
int tot;
int du[maxn],top[maxn];
bool ha[maxn];

void add(int x,int y)
{
    ver[++tot]=y,nt[tot]=head[x],head[x]=tot;
    vv[tot]=x,nn[tot]=hh[y],hh[y]=tot;
    du[y]++;
}

void topo(void)
{
    queue<int>q;
    for(int i=1;i<=n;i++)
        if(du[i]==0) q.push(i);

    int cnt=0;
    while(q.size())
    {
        int x=q.front();
        q.pop();
        top[++cnt]=x;
        for(int i=head[x];i;i=nt[i])
        {
            int y=ver[i];
            du[y]--;
            if(du[y]==0) q.push(y);
        }
    }
}

int LCA(int x,int y)
{
    if(d[x]>d[y]) swap(x,y);
    for(int i=t;i>=0;i--)
        if(d[f[y][i]]>=d[x]) y=f[y][i];
    if(y==x) return x;
    for(int i=t;i>=0;i--)
        if(f[y][i]!=f[x][i]) x=f[x][i],y=f[y][i];
    return f[x][0];
}

int main(void)
{
    int x,root;
    scanf("%d",&n);
    root=n+1;
    t=log(n)/log(2)+1;
    for(int i=1;i<=n;i++)
    {
        si[i]=1;
        while(scanf("%d",&x),x)
        {
            ha[i]=true;
            add(x,i);
        }
    }

    topo();

    for(int i=1;i<=n;i++)
    {
        int x=top[i];
        if(!ha[x])
        {
            d[x]=1;
            f[x][0]=root;
            continue;
        }

        int lca=vv[hh[x]];
        for(int j=hh[x];j;j=nn[j])
            lca=LCA(lca,vv[j]);

        d[x]=d[lca]+1;
        f[x][0]=lca;
        for(int j=1;j<=t;j++)
            f[x][j]=f[f[x][j-1]][j-1];
    }

    for(int i=n;i>=1;i--)
        si[f[top[i]][0]]+=si[top[i]];
    for(int i=1;i<=n;i++)
        printf("%d\n",si[i]-1);

    return 0;
}

例②:Blow up the city HDU - 6604:

Country A and B are at war. Country A needs to organize transport teams to deliver supplies toward some command center cities.

In order to ensure the delivery works efficiently, all the roads in country A work only one direction. Therefore, map of country A can be regarded as DAG( Directed Acyclic Graph ). Command center cities only received supplies and not send out supplies.

Intelligence agency of country B is credibly informed that there will be two cities carrying out a critical transporting task in country A.

As long as any one of the two cities can not reach a command center city, the mission fails and country B will hold an enormous advantage. Therefore, country B plans to destroy one of the n cities in country A and all the roads directly connected. (If a city carrying out the task is also a command center city, it is possible to destroy the city to make the mission fail)

Now country B has made q hypotheses about the two cities carrying out the critical task.
Calculate the number of plan that makes the mission of country A fail.
Input
The first line contains a integer T (1≤T≤10), denoting the number of test cases.

In each test case, the first line are two integers n,m, denoting the number of cities and roads(1≤n≤100,000,1≤m≤200,000).
Then m lines follow, each with two integers u and v, which means there is a directed road from city u to v (1≤u,v≤n,u≠v).

The next line is a integer q, denoting the number of queries (1≤q≤100,000)
And then q lines follow, each with two integers a and b, which means the two cities carrying out the critical task are a and b (1≤a,b≤n,a≠b).

A city is a command center if and only if there is no road from it (its out degree is zero).
Output
For each query output a line with one integer, means the number of plan that makes the mission of country A fail.
Sample Input
2
8 8
1 2
3 4
3 5
4 6
4 7
5 7
6 8
7 8
2
1 3
6 7
3 2
3 1
3 2
2
1 2
3 1
Sample Output
4
3
2
2

题解:当时做的时候就知道是支配树,奈何那时不会写。
就是求某两个点到根节点的必经点有多少个,求出每个点到根节点的必经点,再减去重复的就好了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<set>
#include<deque>
#include<map>
#include<vector>
#include<cmath>
#define ll long long
#define llu unsigned ll
#define pr pair<int,int>
using namespace std;
const ll lnf=0x3f3f3f3f3f3f3f3f;
const int inf = 0x3f3f3f3f;
const int maxn=200100;
const int mod=1e9+7;
int head[maxn],ver[maxn],nt[maxn];
int hh[maxn],vv[maxn],nn[maxn];
int d[maxn],f[maxn][22],t,n,m,q;
int tot;
int du[maxn],top[maxn];

void init(void)
{
    memset(head,0,sizeof(head));
    memset(hh,0,sizeof(hh));
    memset(du,0,sizeof(du));
    memset(f,0,sizeof(f));
    tot=0;
}

void add(int x,int y)
{
    ver[++tot]=y,nt[tot]=head[x],head[x]=tot;
    vv[tot]=x,nn[tot]=hh[y],hh[y]=tot;
    du[y]++;
}

void topo(void)
{
    queue<int>q;
    for(int i=1;i<=n;i++)
        if(du[i]==0) q.push(i);

    int cnt=0;
    while(q.size())
    {
        int x=q.front();
        q.pop();
        top[++cnt]=x;
        for(int i=head[x];i;i=nt[i])
        {
            int y=ver[i];
            du[y]--;
            if(du[y]==0) q.push(y);
        }
    }
}

int LCA(int x,int y)
{
    if(d[x]>d[y]) swap(x,y);
    for(int i=t;i>=0;i--)
        if(d[f[y][i]]>=d[x]) y=f[y][i];
    if(y==x) return x;
    for(int i=t;i>=0;i--)
        if(f[y][i]!=f[x][i]) x=f[x][i],y=f[y][i];
    return f[x][0];
}

int main(void)
{
    int tt;
    scanf("%d",&tt);
    while(tt--)
    {
        init();
        int x,y,root;
        scanf("%d%d",&n,&m);
        root=n+1;
        t=log(n+1)/log(2)+1;

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

        topo();
        d[root]=0;
        for(int i=1;i<=n;i++)
        {
            int x=top[i];
            if(!hh[x])
            {
                d[x]=d[root]+1;
                f[x][0]=root;
                continue;
            }

            int lca=vv[hh[x]];
            for(int j=hh[x];j;j=nn[j])
                lca=LCA(lca,vv[j]);

            d[x]=d[lca]+1;
            f[x][0]=lca;
            for(int j=1;j<=t;j++)
                f[x][j]=f[f[x][j-1]][j-1];
        }

        scanf("%d",&q);
        for(int i=1;i<=q;i++)
        {
            scanf("%d%d",&x,&y);
            printf("%d\n",d[x]+d[y]-d[LCA(x,y)]);
        }

    }

    return 0;
}

二、有向有环图的必经点:

例①:P5180 【模板】支配树:

题目背景
模板题,无背景

题目描述
给定一张有向图,求从1号点出发,每个点能支配的点的个数(包括自己)

输入格式
第一行两个正整数n,m,表示点数和边数 接下来m行,每行输入两个整数u,v表示有一条u到v的有向边

输出格式
一行输出n个整数,表示每个点能支配的点的个数

输入输出样例
输入 #1 复制
10 15
1 2
2 3
3 4
3 5
3 6
4 7
7 8
7 9
7 10
5 6
6 8
7 8
4 1
3 6
5 3
输出 #1 复制
10 9 8 4 1 1 3 1 1 1
说明/提示
n<2 * 105,m<3 * 10^5

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<set>
#include<deque>
#include<map>
#include<vector>
#include<cmath>
#define ll long long
#define llu unsigned ll
#define pr pair<int,int>
using namespace std;
const ll lnf=0x3f3f3f3f3f3f3f3f;
const int inf = 0x3f3f3f3f;
const int maxn=300100;
const int mod=1e9+7;
struct node
{
  int head[maxn],ver[maxn],nt[maxn];
  int tot;
  node(){tot=0;}
  void add(int x,int y)
  {
      ver[++tot]=y,nt[tot]=head[x],head[x]=tot;
  }
}g,rg,semig,dom;

int dfn[maxn],id[maxn],f[maxn],val[maxn],cnt,n,m;
int semi[maxn],idom[maxn],fa[maxn],ans[maxn];

void init(int n)
{
    cnt=0;
    for(int i=1;i<=n;i++)
        f[i]=semi[i]=val[i]=i;
}

void dfs(int x)
{
    dfn[x]=++cnt;
    id[cnt]=x;
    for(int i=g.head[x];i;i=g.nt[i])
    {
        int y=g.ver[i];
        if(dfn[y]) continue;
        fa[y]=x;
        dfs(y);
    }
}

int fi(int x)
{
    if(f[x]!=x)
    {
        int t=f[x];
        f[x]=fi(f[x]);
        if(dfn[semi[val[t]]]<dfn[semi[val[x]]]) val[x]=val[t];
    }
    return f[x];
}

void tarjan(void)
{
    for(int j=cnt;j>=2;j--)
    {
        int x=id[j];
        for(int i=rg.head[x];i;i=rg.nt[i])
        {
            int y=rg.ver[i];
            if(!dfn[y]) continue;
            fi(y);
            if(dfn[semi[val[y]]]<dfn[semi[x]])
                semi[x]=semi[val[y]];
        }

        semig.add(semi[x],x);
        x=f[x]=fa[x];
        for(int i=semig.head[x];i;i=semig.nt[i])
        {
            int y=semig.ver[i];
            fi(y);
            if(semi[val[y]]==x) idom[y]=x;
            else idom[y]=val[y];
        }
    }

    for(int i=2;i<=cnt;i++)
    {
        int x=id[i];
        if(idom[x]!=semi[x]) idom[x]=idom[idom[x]];
        dom.add(idom[x],x);
    }
}

void dfs1(int x)
{
    ans[x]=1;
    for(int i=dom.head[x];i;i=dom.nt[i])
    {
        int y=dom.ver[i];
        dfs1(y);
        ans[x]+=ans[y];
    }
}

int main(void)
{
    scanf("%d%d",&n,&m);
    int x,y;
    init(n);

    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        g.add(x,y);
        rg.add(y,x);
    }
    dfs(1);
    tarjan();
    dfs1(1);
    for(int i=1;i<=n;i++)
    {
        if(i!=1) putchar(' ');
        printf("%d",ans[i]);
    }
    return 0;
}

例②:
Counting on a directed graph CodeChef - GRAPHCNT

题意:
给出一个有 n 个节点,m 条边的有向图,询问有多少对点对 (x,y),满足存在两条路径(一条从 1 到 x,一条从 1 到 y),使得这两条路径的交点只有 1。

数据范围:1≤n≤1e5,m≤5×1e5。

思路:

我们先以 1为根建一颗支配树,可以发现,当 lca(x,y)=1时,(x,y)就是满足条件的点对。

因为如果 lca(x,y)不为 1 ,那么这两条路径都必过 lca(x,y)这个点,不满足题目条件。

那么现在的问题就是如何计算 lca(x,y)=1的点对数量了。

不难看出,只有当 x和 y 位于1 的不同子树时,lca(x,y) 才是 1。因此我们统计出 1 的所有子树的 size就可以了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<set>
#include<deque>
#include<map>
#include<vector>
#include<cmath>
#define ll long long
#define llu unsigned ll
#define pr pair<int,int>
using namespace std;
const ll lnf=0x3f3f3f3f3f3f3f3f;
const int inf = 0x3f3f3f3f;
const int maxn=500100;
const int mod=1e9+7;
struct node
{
  int head[maxn],ver[maxn],nt[maxn];
  int tot;
  node(){tot=0;}
  void add(int x,int y)
  {
      ver[++tot]=y,nt[tot]=head[x],head[x]=tot;
  }
}g,rg,semig,dom;

int dfn[maxn],id[maxn],f[maxn],val[maxn],cnt,n,m;
int semi[maxn],idom[maxn],fa[maxn],ans[maxn];
ll res=0;

void init(int n)
{
    cnt=0;
    for(int i=1;i<=n;i++)
        f[i]=semi[i]=val[i]=i;
}

void dfs(int x)
{
    dfn[x]=++cnt;
    id[cnt]=x;
    for(int i=g.head[x];i;i=g.nt[i])
    {
        int y=g.ver[i];
        if(dfn[y]) continue;
        fa[y]=x;
        dfs(y);
    }
}

int fi(int x)
{
    if(f[x]!=x)
    {
        int t=f[x];
        f[x]=fi(f[x]);
        if(dfn[semi[val[t]]]<dfn[semi[val[x]]]) val[x]=val[t];
    }
    return f[x];
}

void tarjan(void)
{
    for(int j=cnt;j>=2;j--)
    {
        int x=id[j];
        for(int i=rg.head[x];i;i=rg.nt[i])
        {
            int y=rg.ver[i];
            if(!dfn[y]) continue;
            fi(y);
            if(dfn[semi[val[y]]]<dfn[semi[x]])
                semi[x]=semi[val[y]];
        }

        semig.add(semi[x],x);
        x=f[x]=fa[x];
        for(int i=semig.head[x];i;i=semig.nt[i])
        {
            int y=semig.ver[i];
            fi(y);
            if(semi[val[y]]==x) idom[y]=x;
            else idom[y]=val[y];
        }
    }

    for(int i=2;i<=cnt;i++)
    {
        int x=id[i];
        if(idom[x]!=semi[x]) idom[x]=idom[idom[x]];
        dom.add(idom[x],x);
    }
}

void dfs1(int x)
{
    ans[x]=1;
    for(int i=dom.head[x];i;i=dom.nt[i])
    {
        int y=dom.ver[i];
        dfs1(y);
        if(x==1)
            res+=1ll*ans[y]*ans[x];
        ans[x]+=ans[y];
    }
}

int main(void)
{
    scanf("%d%d",&n,&m);
    int x,y;
    init(n);

    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        g.add(x,y);
        rg.add(y,x);
    }
    dfs(1);
    tarjan();
    dfs1(1);
    printf("%lld\n",res);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值