洛谷·[HNOI2015]落忆枫音

初见安~这里是传送门:洛谷P3244

题目描述

「恒逸,你相信灵魂的存在吗?」 郭恒逸和姚枫茜漫步在枫音乡的街道上。望着漫天飞舞的红枫,枫茜突然问出这样一个问题。

「相信吧。不然我们是什么,一团肉吗?要不是有灵魂......我们也不可能再见到你姐姐吧。」 恒逸给出了一个略微无厘头的回答。枫茜听后笑了笑。 「那你仔细观察过枫叶吗?」 说罢,枫茜伸手,接住了一片飘落的枫叶。

「其实每一片枫叶都是有灵魂的。你看,枫叶上不是有这么多脉络吗?我听说,枫叶上有一些特殊的位置,就和人的穴位一样。脉络都是连接在这些穴位之间的。枫树的灵魂流过每片枫叶的根部,沿着这些脉络,慢慢漫进穴位,沁入整片枫叶。也是因为这个原因,脉络才都是单向的,灵魂可不能倒着溜回来呢。」 恒逸似懂非懂地点了点头。枫茜接着说了下去。

「正是因为有了灵魂,每片枫叶才会与众不同。也正是因为有了灵魂,每片枫叶也都神似其源本的枫树,就连脉络也形成了一棵树的样子。但如果仔细看的话,会发现,在脉络树之外,还存在其它的非常细的脉络。虽然这些脉络并不在树上,但他们的方向也同样顺着灵魂流淌的方向,绝不会出现可能使灵魂倒流的回路。」 恒逸好像突然想到了什么。 「那这些脉络岂不是可以取代已有的脉络,出现在脉络树上?」 枫茜闭上了眼睛。

「是啊,就是这样。脉络树并不是唯一的。只要有一些微小的偏差,脉络树就可能差之万里,哪怕是在这同一片枫叶上。就像我们的故事,结局也不是唯一的。只要改变一个小小的选项,故事流程可能就会被彻底扭转。」

「真是深奥啊......」 恒逸盯着这片红枫,若有所思地说。枫茜继续说道。

「还不止如此呢。所有的脉络都不会永恒存在,也不会永恒消失。不管是脉络树上的脉络,还是之外的细小脉络,都是如此。存在的脉络可能断开消失,消失的脉络也可能再次连接。万物皆处在永恒的变化之中,人与人之间的羁绊也是。或许有一天,我们与大家的羁绊也会如同脉络一样,被无情地斩断。或许我们也终将成为”枫音乡的过客“。或许这一切都会是必然,是枫树的灵魂所决定的......」

枫茜的眼角泛起了几滴晶莹剔透的泪珠。恒逸看着这样的枫茜,将她抱入怀中。

「别这样想,枫茜。就算脉络断开,也有可能还会有新的脉络树,也还会与枫树的根相连。这样的话,我们的羁绊仍然存在,只是稍微绕了一些远路而已。无论如何,我都不会离开你的。因为你是我穷尽一生所寻找的,我的真恋啊!」

两人的目光对上了。枫茜幸福地笑了,把头埋进了恒逸的怀抱。从远方山上的枫林中,传来了枫的声音。

【问题描述】 不妨假设枫叶上有 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 取模得到的结果。

输入格式

输入文件的第一行包含四个整数 n、m、x和y,依次代表枫叶上的穴位数、脉络数,以及要添加的脉络是从穴位 x连向穴位y的。 接下来 m行,每行两个整数,由空格隔开,代表一条脉络。第 i 行的两个整数为ui和vi,代表第 i 条脉络是从穴位 ui连向穴位vi的。

输出格式

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

输入 

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

输出 

3

说明/提示

对于所有测试数据,1 \leq n \leq 100000, n - 1 \leq m \leq min(200000, \frac {n(n -1)}{2}),1 \leq x, y, u_i, v_i \leq n

题解

一看就不是本蒟蒻可以自己写出来的题【噗】关键是题解都看了两个半小时才看懂……果然还是太蠢了。

首先【就像所有别的题解一样】如果不加边,这就是一个DAG,方案数为::\prod {deg[i]}。deg为每个点的入度,也就是每个点可以自由选择自己的父亲节点。

加了边过后,因为有可能形成环,【不形成的话我们就无所谓了】,就有可能环上的每个点选择的父亲都刚好是环上的点,就导致这样形成的方案是不联通的,有环了。而这种情况出现的次数就是所有不在环上的点贡献出来的次数。相当于是设不在环上的点的集合为S,则 环出现的次数就是     \prod_{i \in S} deg[i] 。【环以外的所有点仍然可以自由选择父亲】

所以现在我们要求的就是环以外的所有点对不合法答案的贡献。换句话说就是要找到环上的点并将其贡献去掉,剩下的就是环外的贡献了

这里我们可以用dp来求解。有拓扑dp的解法,但是这里就只讲记忆化搜索的方法了【蒟蒻懒】。我们考虑:如果成环,假设加的边是x -> y的,那么添加之前必然有y -> x的路径存在。所以我们就从点y开始dfs搜索,只要搜到了x,就说明这个图加上了 x -> y这条边后成了环,并且从y搜到x的这一路上的点都是环以内的点。所以相当于环上的点我们已经找到了,设数组f[u]到点u对不合法情况的贡献。到了x,那么其不合法情况数就是\frac{\prod{deg[i]}}{deg[x]}【除去x这个环上点的贡献】,然后一路回溯时继续除掉环上的点的贡献,直到回到y时就可以得到剩下来的 \prod_{i \in S} deg[i],我们用\prod {deg[i]}减掉就行了。当然,如果没有找到x,返回的就会是0;如果有多条路径,我们回溯的时候先累加,再一起除以当前点的deg就可以了。

最后的最后有一个小细节就是:deg[y]我们要不要因为加的这条边而+1呢?计算\prod {deg[i]}的时候当然是要+1的,但是对于记忆化搜索的时候就无所谓了——因为如果有环的话,不管deg[y]是多少都会被除掉的;没有环的话,dfs的答案都会是0。所以只要一致就行了。

呼……上代码:)

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define maxn 100005
using namespace std;
const int mod = 1e9 + 7;
typedef long long ll;
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}

struct edge {
	int to, nxt;
	edge() {}
	edge(int t, int nn) {to = t, nxt = nn;}
}e[maxn  << 1];

int head[maxn], k = 0;
void add(int u, int v) {e[k] = edge(v, head[u]), head[u] = k++;}

int n, m, x, y;
int deg[maxn];
ll f[maxn], ans = 1, sum = 1, inv[maxn];
void dfs(int u) {
	if(u == x) {f[x] = 1ll * sum * inv[deg[x]] % mod; return;}//从sum开始回溯找不合法数。
	if(~f[u]) return;
	
	f[u] = 0;//表示至少走过了
	for(int i = head[u]; ~i; i = e[i].nxt) {
		register int v = e[i].to;
		dfs(v); f[u] = 1ll * (f[u] + f[v]) % mod;//累加
	}
	f[u] = 1ll * f[u] * inv[deg[u]] % mod;//重点
}

signed main() {
	memset(head, -1, sizeof head);
	n = read(), m = read(), x = read(), y = read();
	for(register int u, v, i = 1; i <= m; i++) {
		u = read(), v = read(), add(u, v); deg[v]++;//统计deg入度
	}
	
	inv[0] = inv[1] = 1; deg[1]++;
	for(int i = 2; i <= n; i++)	inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod;
	//这里可以先预处理出逆元,是记忆化搜索的时候会用到的。

	for(int i = 1; i <= n; i++) {
		if(i == y) ans = 1ll * ans * (deg[i] + 1) % mod;//这里我选择ans和sum分开……
		else ans = 1ll * ans * deg[i] % mod;//如果不分开的话dfs时还要特判一下。两者差不多麻烦。
		sum = sum * deg[i] % mod;//sum用于记忆化搜索
	}
	
	memset(f, -1, sizeof f);//-1表示没有遍历过
	dfs(y);
	printf("%lld\n", (ans - f[y] + mod) % mod);
	return 0;
}

这个题真的很经典了,不管是题目背景还是其思维难度……

迎评:)
——End——

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值