http://codeforces.com/contest/1395

E. Boboniu Walks on Graph

 

题意:给定一张有向图,有边权,每点出度最多为k,k9k\le 9,每一个出度为 i 的点取第 cic_i 小的出边,这些 c1,c2,,ckc_1,c_2,\cdots,c_k 组成一个tuple,问有多少种tuple,使得每个点都至少在某个环里。

集合哈希

由于每点取一条出边,所以要每点在一个环里,等价于每点被一条边指着。

k 很小,可以直接暴力枚举tuple中每个数,需要 O(1)O(1) 判断这个tuple是否满足条件。

先预处理出出度为 i 的点,所有第 j 小的边,并对这个集合进行哈希。对于一个tuple,可以把这 i 个集合哈希的合并,判断是否每点涉及一次。

哈希可以通过给每个数随机一个值,集合的哈希值等于所有数的哈希值的和或者乘积,或者其它满足合并律的操作。

下面的代码是用类似十进制的方式,其实并不严谨,因为可能并不涉及所有数,或者某个数涉及多次,但是哈希值与只涉及一次时相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const int N = 5e6 + 10;
const ll mod = 1e9 + 7;
int n, m, k;
typedef pair<int, int>pii;
vector<pii>G[N];
vector<int>vc[10][10];
ll all;
ll p[N], a[10][10], t[10];
ll ans;
void dfs(int dep) {
if (dep == k + 1) {
ll tmp = 0ll;
for (int i = 1; i <= k; i++) {
tmp = (tmp + a[i][t[i]]) % mod;
}
if (tmp == all)ans++;
return;
}
for (int i = 1; i <= dep; i++) {
t[dep] = i;
dfs(dep + 1);
}
}
int main() {
scanf("%d%d%d", &n, &m, &k);
p[0] = 1ll;
for (int i = 1; i <= n; i++)p[i] = p[i - 1] * 10 % mod;
for (int i = 1; i <= n; i++) {
all = (all + i * p[i] % mod) % mod;
}
for (int i = 1; i <= m; i++) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
G[u].push_back(pii(w, v));
}
for (int i = 1; i <= n; i++) {
sort(G[i].begin(), G[i].end());
for (int j = 1; j <= (int)G[i].size(); j++)vc[(int)G[i].size()][j].push_back(G[i][j - 1].second);
}
for (int i = 1; i <= k; i++) {
for (int j = 1; j <= i; j++) {
for (int u : vc[i][j]) {
a[i][j] = (a[i][j] + u * p[u] % mod) % mod;
}
}
}
dfs(1);
printf("%lld\n", ans);
return 0;
}