zoukankan      html  css  js  c++  java
  • BZOJ4011:[HNOI2015]落忆枫音(DP,拓扑排序)

    Description

    「恒逸,你相信灵魂的存在吗?」 

    郭恒逸和姚枫茜漫步在枫音乡的街道上。望着漫天飞舞的红枫,枫茜突然问出这样一个问题。 
    「相信吧。不然我们是什么,一团肉吗?要不是有灵魂……我们也不可能再见到你姐姐吧。」 
    恒逸给出了一个略微无厘头的回答。枫茜听后笑了笑。
    「那你仔细观察过枫叶吗?」 
    说罢,枫茜伸手,接住了一片飘落的枫叶。 
    「其实每一片枫叶都是有灵魂的。你看,枫叶上不是有这么多脉络吗?我听说,枫叶上有一些特殊的位置,就和人的穴位一样。脉络都是连接在这些穴位之间的。枫树的灵魂流过每片枫叶的根部,沿着这些脉络,慢慢漫进穴位,沁入整片枫叶。也是因为这个原因,脉络才都是单向的,灵魂可不能倒着溜回来呢。」 
    恒逸似懂非懂地点了点头。枫茜接着说了下去。 
    「正是因为有了灵魂,每片枫叶才会与众不同。也正是因为有了灵魂,每片枫叶也都神似其源本的枫树,就连脉络也形成了一棵树的样子。但如果仔细看的话,会发现,在脉络树之外,还存在其它的非常细的脉络。虽然这些脉络并不在树上,但他们的方向也同样顺着灵魂流淌的方向,绝不会出现可能使灵魂倒流的回路。」  
    恒逸好像突然想到了什么。 
    「那这些脉络岂不是可以取代已有的脉络,出现在脉络树上?」 
    枫茜闭上了眼睛。 
    「是啊,就是这样。脉络树并不是唯一的。只要有一些微小的偏差,脉络树就可能差之万里,哪怕是在这同一片枫叶上。就像我们的故事,结局也不是唯一的。只要改变一个小小的选项,故事流程可能就会被彻底扭转。」 
    「真是深奥啊……」 
    恒逸盯着这片红枫,若有所思地说。枫茜继续说道。 
    「还不止如此呢。所有的脉络都不会永恒存在,也不会永恒消失。不管是脉络树上的脉络,还是之外的细小脉络,都是如此。存在的脉络可能断开消失,消失的脉络也可能再次连接。万物皆处在永恒的变化之中,人与人之间的羁绊也是。或许有一天,我们与大家的羁绊也会如同脉络一样,被无情地斩断。或许我们也终将成为“枫音乡的过客”。或许这一切都会是必然,是枫树的灵魂所决定的……」 
    枫茜的眼角泛起了几滴晶莹剔透的泪珠。恒逸看着这样的枫茜,将她抱入怀中。  
    「别这样想,枫茜。就算脉络断开,也有可能还会有新的脉络树,也还会与枫树的根相连。这样的话,我们的羁绊仍然存在,只是稍微绕了一些远路而已。无论如何,我都不会离开你的。因为你是我穷尽一生所寻找的,我的真恋啊!」 
    两人的目光对上了。枫茜幸福地笑了,把头埋进了恒逸的怀抱。从远方山上的枫林中,传来了枫的声音。 
    【问题描述】 
    不妨假设枫叶上有 n个穴位,穴位的编号为 1 ~  n。有若干条有向的脉络连接
    着这些穴位。穴位和脉络组成一个有向无环图——称之为脉络图(例如图 1),穴
    位的编号使得穴位 1 没有从其他穴位连向它的脉络,即穴位 1 只有连出去的脉络;
    由上面的故事可知,这个有向无环图存在一个树形子图,它是以穴位 1为根的包含
    全部n个穴位的一棵树——称之为脉络树(例如图 2和图 3给出的树都是图1给出
    的脉络图的子图);值得注意的是,脉络图中的脉络树方案可能有多种可能性,例
    如图2和图 3就是图 1给出的脉络图的两个脉络树方案。 
           
    脉络树的形式化定义为:以穴位 r 为根的脉络树由枫叶上全部 n个穴位以及 n-  1 条脉络组成,脉络树里没有环,亦不存在从一个穴位连向自身的脉络,且对于枫叶上的每个穴位 s,都存在一条唯一的包含于脉络树内的脉络路径,使得从穴位r 出发沿着这条路径可以到达穴位 s。
    现在向脉络图添加一条与已有脉络不同的脉络(注意:连接 2个穴位但方向不同的脉络是不同的脉络,例如从穴位3到4的脉络与从4到3的脉络是不同的脉络,因此,图 1 中不能添加从 3 到 4 的脉络,但可添加从 4 到 3 的脉络),这条新脉络可以是从一个穴位连向自身的(例如,图 1 中可添加从 4 到 4 的脉络)。原脉络图添加这条新脉络后得到的新脉络图可能会出现脉络构成的环。
    请你求出添加了这一条脉络之后的新脉络图的以穴位 1 为根的脉络树方案数。由于方案可能有太多太多,请输出方案数对 1,000,000,007 取模得到的结果。 

    Input

    输入文件的第一行包含四个整数 n、m、x和y,依次代表枫叶上的穴位数、脉络数,以及要添加的脉络是从穴位 x连向穴位y的。 

    接下来 m行,每行两个整数,由空格隔开,代表一条脉络。第 i 行的两个整数为ui和vi,代表第 i 条脉络是从穴位 ui连向穴位vi的。 

    Output

    输出一行,为添加了从穴位 x连向穴位 y的脉络后,枫叶上以穴位 1 为根的脉络树的方案数对 1,000,000,007取模得到的结果。 

    Sample Input

    4 4 4 3
    1 2
    1 3
    2 4
    3 2

    Sample Output

    3

    HINT

    对于所有测试数据,1 <= n <= 100000,n - 1 <= m <= min(200000, n(n – 1) / 2),

    1 <= x, y, ui, vi <= n。

    Solution 

    QwQ挺(想)有(不)意(出)思(来)的一道题。
    假设一开始不加x->y那条边,每一个非1的点选择一条入边就可以形成一棵树。正确性好像是从朱刘算法的推论得来的……
    那么一开始的答案显然就是$prod_{i=2}^nInd_i$,$Ind_i$表示$i$点的入度。
    接着考虑加上x->y那条边,还按上面那样计算答案,显然会多出来成环这种不合法的方案。我们只要简单的容斥思想,计算出成环的方案然后用总方案减掉就是答案了。
    什么时候才会成环呢?因为加入x->y这条边才成环的,所以x->y边一定是被选中的。所以任意一条y到x的路径都可以和边x->y成环。
    这玩意儿可以拓扑排序的时候DP来做,设$f[i]$表示y到i路径的答案。
    在从i转移到j的时候,j由随机选入边变成了强制选成环的边,所以要乘一个j的入度的逆元。

    Code

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<queue>
     4 #define N (100009)
     5 #define MOD (1000000007)
     6 using namespace std;
     7 
     8 struct Edge{int to,next;}edge[N<<1];
     9 int n,m,u,v,S,T,Ind[N],head[N],num_edge;
    10 long long inv[N],f[N],pinv[N],ans=1;
    11 queue<int>q;
    12 
    13 void add(int u,int v)
    14 {
    15     edge[++num_edge].to=v;
    16     edge[num_edge].next=head[u];
    17     head[u]=num_edge; Ind[v]++;
    18 }
    19 
    20 void Toposort()
    21 {
    22     for (int i=1; i<=n; ++i)
    23         if (!Ind[i]) q.push(i);
    24     f[T]=ans*pinv[T]%MOD;
    25     while (!q.empty())
    26     {
    27         int x=q.front(); q.pop();
    28         for (int i=head[x]; i; i=edge[i].next)
    29         {
    30             Ind[edge[i].to]--;
    31             if (!Ind[edge[i].to]) q.push(edge[i].to);
    32             f[edge[i].to]+=f[x]*pinv[edge[i].to]%MOD;
    33             f[edge[i].to]%=MOD;
    34         }
    35     }
    36     printf("%lld
    ",((ans-f[S])%MOD+MOD)%MOD);
    37 }
    38 
    39 int main()
    40 {
    41     scanf("%d%d%d%d",&n,&m,&S,&T);
    42     inv[1]=1; ++Ind[T];//注意Ind的变化 
    43     for (int i=2; i<=n; ++i)
    44         inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
    45     for (int i=1; i<=m; ++i)
    46         scanf("%d%d",&u,&v),add(u,v);
    47     for (int i=2; i<=n; ++i)
    48         ans=ans*Ind[i]%MOD;
    49     if (T==1){printf("%lld
    ",ans); return 0;}
    50     for (int i=1; i<=n; ++i) pinv[i]=inv[Ind[i]];
    51     --Ind[T]; Toposort();
    52 }
  • 相关阅读:
    高级查询
    简单查询
    CRUD
    T-SQL语句
    数据库规范
    导出含有特定字符串的注册表
    .net、jquery、ajax、wcf实现数据库用户名检测局部刷新
    数据结构实验之二叉树二:遍历二叉树
    传纸条
    数据结构实验之栈与队列五:下一较大值(一)
  • 原文地址:https://www.cnblogs.com/refun/p/9617138.html
Copyright © 2011-2022 走看看