您的位置:首页 > 其它

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

2016-04-04 17:34 399 查看
来源:知乎

题意请戳上方↑

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

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
;
int scc, belong
, sta
, top, dfn
, low
, tim;
int ans, d, D;
bool check
;
int dep
;
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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: