大致题意:
有 n n n 个点 m m m 条边的有向图,边的权值分别为 1 1 1 ~ m m m 互不相同,且每个点的出度不超过 k k k ,现在有一个 k k k 元组 ( c 1 , c 2 , … , c k ) (c_1,c_2,\ldots ,c_k) (c1,c2,…,ck) ,对于某个出度为 o u t u out_u outu 的点 u u u ,仅保留以 u u u 为起点权值第 c d e g u c_{deg_u} cdegu 小的边,要求最后形成的有向图每个点都能回到自身,即有向图形成一个环;求满足条件的 k k k 元组的个数;
2 ≤ n ≤ 2 ⋅ 1 0 5 , 2 ≤ m ≤ m i n ( 2 ⋅ 1 0 5 , n ( n − 1 ) ) , 1 ≤ k ≤ 9 2 \leq n \leq 2 \cdot 10^5,2 \leq m \leq min(2 \cdot 10^5,n(n-1)),1 \leq k \leq 9 2≤n≤2⋅105,2≤m≤min(2⋅105,n(n−1)),1≤k≤9
分析:
k k k 值较小,枚举所有的 k k k 元组的复杂度是 O ( k ! ) O(k!) O(k!) ,可以考虑,问题是如何快速判断 k k k 元组是否合法;按题意,最后形成的有向图即一个有向环,所以保留下来的边等价于遍历完一遍节点 1 1 1 ~ n n n ;我们 h a s h hash hash 一下点权,判断 k k k 元组对应遍历的点权和是否等于 1 1 1 ~ n n n 的点权和即可;
代码:
#include<bits/stdc++.h>
#define pb push_back
#define ll long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define frep(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N = 2E5+10;
const ll MOD = 1E9+7;
int n,m,k;
int s[N][2];
vector<int>G[N];
ll sum[10][10];
ll cnt,rd[N],ans[10],ct=0;
// rd[i]: 节点i的点权(hash)
// cnt: 节点1~n的点权和
// ans[]: 枚举的k元组
// ct: 满足条件的k元组数量
// sum[i][j]: 出度为i的所有点的第j小的边对应的点权和
void check()
{
ll v=0;
rep(i,1,k) v+=sum[i][ans[i]];
if(v==cnt) ct++;
return;
}
void dfs(int d){
if(d>k) return check();
for(ans[d]=1;ans[d]<=d;ans[d]++) dfs(d+1);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m>>k;
rep(i,1,m){
int u,v,w;
cin>>u>>v>>w;
s[w][0]=u,s[w][1]=v;
}
rep(i,1,m) G[s[i][0]].pb(s[i][1]);
rep(i,1,n) rd[i]=rand()%MOD,cnt+=rd[i];
rep(i,1,n)rep(j,0,G[i].size()-1)
sum[G[i].size()][j+1]+=rd[G[i][j]];
dfs(1);
cout<<ct;
}