题意:幼儿园里有N个盒子,每个盒子有一种颜色,每个盒子中可以装M个与盒子颜色相同的拼图,但是现在有些孩子将拼图放错了盒子,幼儿园老师要将放错的拼图放到正确的盒子里,她每次可以拿一块拼图从这个盒子到另一个盒子,也可以什么都不拿都另一个盒子,问这个老师最少要移动多少次手才能整理好这些拼图。
思路:求强连通分支。首先,在同一个强连通分支里的盒子之间是可以互换的,即颜色A的拼图放到了颜色B的盒子了,那么一定可以从颜色B的盒子里取出A然后放回颜色为A的盒子里,然后从A盒子了取出颜色不为A的拼图放回正确的盒子里,所以最少移动的次数就是放错拼图的个数。如果不是一个强连通分支,那么在整理完这个强连通分支里的盒子后,一定会空手到另一个强连通分支去,最少的移动次数是:放错的拼图数+(强连通分支数-1)。另外还要考虑的是,如果这个盒子的拼图都是正确,那么这个盒子就不能加入图中。
代码:

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <iostream> #include <algorithm> #include <queue> #include <math.h> #include <stack> #include <vector> #include <map> #define N 53 #define M 505 using namespace std ; bool mp[M][M] ; int dfn[M] , low[M] ; bool used[M] , vist[M] , f[M] ; int id , ans , cnt , n , m ; stack<int>q ; void init() { memset( mp , false , sizeof( mp )) ; memset( dfn , 0 , sizeof ( dfn )) ; memset( low , 0 , sizeof ( low )) ; memset( used , false , sizeof( used )); memset( vist , false , sizeof ( vist )) ; memset( f , false , sizeof ( f )) ; id = cnt = ans = 0 ; while( !q.empty()) q.pop(); } void Tarjan( int x ) { dfn[x] = low[x] = ++id ; used[x] = vist[x] = true ; q.push( x ) ; for ( int i = 1 ; i <= n ; i++ ) { if ( mp[x][i] ) { if ( !used[i] ) { Tarjan( i ) ; low[x] = min( low[x] , low[i] ) ; } else if ( vist[i] ) { low[x] = min( low[x] , dfn[i] ) ; } } } if ( dfn[x] == low[x] ) { ans++ ; int u ; do { u = q.top() ; q.pop(); vist[u] = false ; }while( u != x ) ; } } int main() { int i , j , x ; while ( scanf ( "%d%d" , &n , &m ) != EOF ) { init() ; for ( i = 1 ; i <= n ; i++ ) { for ( j = 1 ; j <= m ; j++ ) { scanf ( "%d" , &x ) ; if ( x != i ) { mp[x][i] = true ; f[i] = true ; cnt++ ; } } } /*for ( i = 1 ; i <= n ; i++ ) { for ( j = 1 ; j <= n ; j++ ) cout<<mp[i][j]<<" "; cout<<endl ; }*/ for ( i = 1 ; i <= n ; i++ ) if ( !dfn[i] && f[i] ) Tarjan( i ) ; //cout<<ans<<endl ; cnt = cnt + max( ans - 1 , 0 ); cout<<cnt<<endl ; } return 0 ; }