[HNOI 2015]落忆枫音
Description
「恒逸,你相信灵魂的存在吗?」
Input
输入文件的第一行包含四个整数 n、m、x和y,依次代表枫叶上的穴位数、脉
Output
输出一行,为添加了从穴位 x连向穴位 y的脉络后,枫叶上以穴位 1 为根的脉
Sample Input
1 2
1 3
2 4
3 2
Sample Output
HINT
对于所有测试数据,1 <= n <= 100000,n - 1 <= m <= min(200000, n(n – 1) / 2),
题解
首先我们简化这个问题,考虑一个DAGDAG中树形图的个数。
值得注意的是这里有朱刘算法的一个推广:
如果除根节点外每个点都选择一条入边,由于没有环,因此一定会形成一个树形图。
这里简要证明一下:
首先证明这个图是联通的。
采用反证法,假设图不联通,显然原图存在一个子图满足:子图联通,并且子图中边数ww>点数uu。
那么这样这个子图的总入度>总点数。那么一定存在一个点入度>11。与题设不符,原命题成立。
下证这个连通图满足树形图的性质。
依旧采用反证法,我们假设选择满足题意的边不能够成树形图。那么存在两条不同的路径S1S1,S2S2从根节点11到达另外一个节点vv。
假设子图g(u′,w′)⊂ 原图G(u,w)是满足S1,S2⊂g(u′,w′)的极小子图。
既然存在两条不同的路径,显然u′<=w′。
又由于除根节点外每个点有且仅有一个入边,显然w′=u′−1。矛盾,原命题成立。
由上述结论,显然简化版的问题答案就是:ans=n∏i=2degreei(其中degreei表示节点i的入度)
我们再来考虑原问题,现在加上额外的一条边之后,原图可能形成环。再由上面的决策,显然不可行。
我们考虑不合法的情况是什么:显然选边的时候构成环就是不合法的。并且这个环中的一条边肯定是x→y。
容易得到的结论就是,需要减去的不合法的值就是∑S是G中y→x的一条路径的点集∏2≤j≤n,j∉Sdegreej
考虑贡献法来计算,用top−sort−dp,记fi表示∑S是G中y→i的一条路径的点集∏2≤j≤n,j∉Sdegreej
dp方程就是fi=∑j→ifjdegreei
初值fy=∏ni=2degreeidegreey
由于1号根节点不选入边,所以1号点显然不能构成环。若y==1,显然这条边不可能被选,不用top−sort−dp,直接输出原始ans即可。
1 //It is made by Awson on 2017.12.24 2 #include <map> 3 #include <set> 4 #include <cmath> 5 #include <ctime> 6 #include <queue> 7 #include <stack> 8 #include <vector> 9 #include <cstdio> 10 #include <string> 11 #include <cstdlib> 12 #include <cstring> 13 #include <iostream> 14 #include <algorithm> 15 #define LL long long 16 #define Max(a, b) ((a) > (b) ? (a) : (b)) 17 #define Min(a, b) ((a) < (b) ? (a) : (b)) 18 using namespace std; 19 const int N = 100000; 20 const int M = 200000; 21 const int MOD = 1000000007; 22 23 int n, m, x, y, u, v; 24 struct tt { 25 int to, next; 26 }edge[M+5]; 27 int path[N+5], top; 28 int ans = 1, degree[N+5], in[N+5]; 29 int f[N+5]; 30 queue<int>Q; 31 32 int quick_pow(int a, int b) { 33 int cnt = 1; 34 while (b) { 35 if (b&1) cnt = (LL)cnt*a%MOD; 36 a = (LL)a*a%MOD; 37 b >>= 1; 38 } 39 return cnt; 40 } 41 void topsort() { 42 f[y] = ans; 43 for (int i = 1; i <= n; i++) if (in[i] == 0) Q.push(i); 44 while (!Q.empty()) { 45 int u = Q.front(); Q.pop(); 46 f[u] = (LL)f[u]*quick_pow(degree[u], MOD-2)%MOD; 47 for (int i = path[u]; i; i = edge[i].next) { 48 (f[edge[i].to] += f[u]) %= MOD; in[edge[i].to]--; 49 if (in[edge[i].to] == 0) Q.push(edge[i].to); 50 } 51 } 52 } 53 void add(int u, int v) { 54 edge[++top].to = v; 55 edge[top].next = path[u]; 56 path[u] = top; 57 } 58 void work() { 59 scanf("%d%d%d%d", &n, &m, &x, &y); 60 degree[y]++; 61 for (int i = 1; i <= m; i++) { 62 scanf("%d%d", &u, &v); 63 add(u, v); degree[v]++; 64 } 65 for (int i = 2; i <= n; i++) ans = (LL)ans*degree[i]%MOD; 66 for (int i = 1; i <= n; i++) in[i] = degree[i]; in[y]--; 67 if (y != 1) topsort(); 68 printf("%d\n", (ans-f[x]+MOD)%MOD); 69 } 70 int main() { 71 work(); 72 return 0; 73 }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】博客园携手 AI 驱动开发工具商 Chat2DB 推出联合终身会员
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET 依赖注入中的 Captive Dependency
· .NET Core 对象分配(Alloc)底层原理浅谈
· 聊一聊 C#异步 任务延续的三种底层玩法
· 敏捷开发:如何高效开每日站会
· 为什么 .NET8线程池 容易引发线程饥饿
· 终于决定:把自己家的能源管理系统开源了!
· 外部H5唤起常用小程序链接规则整理
· C#实现 Winform 程序在系统托盘显示图标 & 开机自启动
· WPF 怎么利用behavior优雅的给一个Datagrid添加一个全选的功能
· 了解 ASP.NET Core 中的中间件