Graph · 图的联通 + 矩阵快速幂

来源:知乎

题意请戳上方↑

首先有两个很好玩的性质:

1.有向图的一个强连通分量的周期d = 所有环的长度的最大公约数

2.有向图的周期D = 所有强连通分量的周期di的最小公倍数


然后如果要求最小的满足,由于k可能很大,那么用类似倍增的思想来求,我一开始傻*用的二分,T出一片天。

另外因为,矩乘的时候要压位。

#include <bits/stdc++.h>
using namespace std;
#define f(i, x, y) for (int i=x; i<=y; i++)
#define ff(i, x, y) for (int i=x; i<y; i++)
#define pb push_back

typedef long long LL;
const int N = 1e5 + 10;
const int mod = 1e9 + 7;
int type, n, m;
vector<int> a[N];
int scc, belong[N], sta[N], top, dfn[N], low[N], tim;
int ans, d, D; 
bool check[N];
int dep[N];
map<int, int> prime;

int gcd(int a, int b){
	while ( b ) b^=a^=b^=a%=b;
	return a;
}

inline void dfs(int t, int depth){
	dep[t]=depth;
	for (int i = 0, v; i<a[t].size(); ++i){
	 	v = a[t][i];
	 	if ( belong[v] == scc ) 
	 		if ( ! dep[v] ) dfs(v, depth + 1);
	 	 	   	else d = gcd(d, abs( dep[t] - dep[v] ) + 1 );
	}
}                                                   	

inline void tarjan(int t){
	dfn[t] = low[t] = ++ tim;
	check[t] = 1; sta[ ++ top ] = t;
	ff(i, 0, a[t].size()){
		int v = a[t][i];
		if ( ! dfn[ v ] ){
			tarjan( v );
			low[t] = min(low[t], low[v]);
		}
		else if (check[v]) low[t] = min(low[t], dfn[v]);
	}
	if ( dfn[t] == low[t] ){
		++ scc; int now = 0;
		while ( now != t ){
			now = sta[ top -- ];
			belong[now] = scc;
			check[now] = 0;
		}
		d=0;
		dfs(t, 1);
		f(i, 2, sqrt(d)) if ( d % i == 0 ){
			int cnt = 0;
			while ( d % i == 0 ) d /= i, ++ cnt;
			prime[i] = max(prime[i], cnt);
		}
		if ( d > 1 ) prime[d] = max(prime[d], 1);
	}
}

/*--------------------divide-----------------------------*/ 

void calc(){
	ans = D = 1;
	for (map<int, int>::iterator i=prime.begin(); i != prime.end(); ++ i)
		f(j, 1, (*i).second)
	 		 ans = 1LL * ans * (*i).first % mod,
			 D = D * (*i).first ;
}

typedef int arr[205];
struct mat{
	arr a[205];
	arr& operator [](int x){return a[x];}
	void init(){memset(a, 0, sizeof a);}
}unit;
int TMP[2][205][15];

mat operator *(mat A, mat B){
	mat tmp; tmp.init();
	memset(TMP, 0, sizeof TMP);
	f(i, 1, n) for (int j=1, k=1, l=0; j <= n; ++ j, ++ l){
		 if ( A[i][j] ) TMP[0][i][k] |= 1<<l;
		 if ( j % 30 == 0 ) l = 0, ++ k;
	}
	f(i, 1, n) for (int j=1, k=1, l=0; j <= n; ++ j, ++ l){
		 if ( B[j][i] ) TMP[1][i][k] |= 1<<l;
		 if ( j % 30 == 0 ) l = 0, ++ k;
	}
	int K = (n + 29) / 30;
	f(i, 1, n) f(j, 1, n) f(k, 1, K)
		 if ( TMP[0][i][k] & TMP[1][j][k]) {
		 	tmp[i][j] = 1;
		 	break;
		 }
    return tmp;
}

bool operator ==(mat A, mat B){
	f(i, 1, n) f(j, 1, n)
		 if ( A[i][j] ^ B[i][j] ) return 0;
    return 1;
}

mat qck(mat a, int b){
	mat ret, ca; ca = a;
	f(i, 1, n) f(j, 1, n) ret[i][j] = (i==j);
	for(; b; b >>= 1, ca = ca * ca)
		if ( b & 1 ) ret = ret * ca;
	return ret;
}

mat A[205], Ad, AD[205];

void work(){
	/*超时的二分判定 
	int l = 1, r = 1e9;
	ad = qck(unit, D);
	while ( l <= r ){
		int mid = (l+r) >> 1;
		mat tmp = qck(unit, mid);
		if ( ! ( tmp == tmp * ad ) )  l = mid + 1;
		   else r = mid - 1, K = mid;
	}
	*/
	
	//这里是用类似倍增的思想来求  A[i]是表示unit的2^i自乘 
	unit.init();
	f(i, 1, n) ff(j, 0, a[i].size()) unit[i][a[i][j]] = 1;
	A[0] = unit; Ad = qck(A[0], D); 
	AD[0] = A[0] * Ad;
	int t = 1;
	for ( ; ; ++ t) {
		A[t] = A[t-1] * A[t-1];
		AD[t] = A[t] * Ad;
		if ( A[t] == A[t] * Ad ) break;
	}
	int P = 0;
	mat tmp; tmp.init(); f(i, 1, n) tmp[i][i] = 1;
	for (int i = t - 1; i >= 0; -- i)
		if ( ! ( tmp * A[i] == tmp * AD[i] ) )
			P += 1<<i, tmp = tmp * A[i];
	printf("%d %d\n", P + 1, ans);
	return ;
}

/*--------------------divide-----------------------------*/ 

int main(){
	freopen("graph.in", "r", stdin);
	freopen("graph.out", "w", stdout);
	scanf("%d%d%d", &n, &m, &type);
	f(i, 1, m) {
		int x, y;
		scanf("%d%d", &x, &y);
		a[x].pb(y); 
	}
	f(i, 1, n) if ( ! dfn[i] )  tarjan(i);
	calc();
	if ( type != 1 ) printf("%d\n", ans); 
		else work();
	//debug();
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值