#664 (Div. 2) - E.Boboniu Walks on Graph

大致题意:

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 2n2105,2mmin(2105,n(n1)),1k9

分析:

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;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值