这是个“牵一发而动全身”的过程。移动一个人的时候,可能会带动一大串同时移动。这样可以把监狱的人划分成好多组,每一组表示要同时移动的一串人。如下图,左边的代表监狱A,右边的代表监狱B。现在问题就变成这样了:两个监狱要以组为单位进行交换。因为最多交换一半,所以选第3、4组进行交换,最后每个监狱的交换人数为3。
从另一个角度,分组后的选择问题,有点像二维背包。每一组有两个值,从A里选的人数和从B里选的人数。即,每组相当于一个物品,有重量和体积。限制条件也有两个:从A中选择的总人数和从B中选择的总人数不能超过监狱容量的一半。即,背包的重量和体积都有限。最终要使交换的人尽可能的多。即,背包装得东西尽可能多。
哈哈,分析到这里,估计就有思路了。问题的求解可分为两个阶段:分组、二维背包。只要这两个部分解决了,问题就解决啦。
“万里长城第一步”------分组。如何分组?DFS or 传递闭包 都可以搞定。
(1) DFS,先A出发,沿冲突边到B,再从B出发沿冲突边到A,如此下去,直到走投无路。
(2)传递闭包,用Floyd。Floyd算法简单,实现无难度。关键是从Floyd的结果构造分组信息。这里不做解释,主要看代码。
“山穷水尽疑无路,柳暗花明又一村”------二维背包。背包问题很经典,要多练习。二维背包用的也是DP。
状态转换方程:dp[k][i][j] = dp[k-1][i-a[k]][j-b[k]] || dp[k-1][i][j]。简单解释一下:dp[k][i][j]表示对前K组,用监狱A的i个人和监狱B的j个人交换是否成功。前K组的解与前K-1组有关。当前K-1组解决后,只要加上第K组就可以搞定前K组。对第K组有两种选择:选或不选。
不选第K组,则dp[K][i][j]能否成功取决于dp[k-1][i][j] 能否成功。
选第K组,则dp[K][i][j]能否成功取决于dp[k-1][i-a[k]][j-b[k]]能否成功。
最后,两种情况只要有一种成功, dp[k][i][j] 就能成功。 好啦,已经说的太多了,具体看代码吧。
代码(1): DSF + 背包
#include <iostream> using namespace std; //***********************常量定义***************************** const int SIZE = 205; //*********************自定义数据结构************************* //********************题目描述中的变量************************ //监狱的人数 int m; //会发生冲突的对数 int r; //**********************算法中的变量************************** //map[i][j]表示i和j是否会冲突 int map[SIZE][SIZE]; //A组里的人数 int aSize; //b组里的人数 int bSize; //dp[i][j] 表示用A组的i个人换B组的j个人是否可行 bool dp[SIZE][SIZE]; //visited[0][i] 表示用A组中的点i是否被访问过 //visited[1][i] 表示用B组中的点i是否被访问过 bool visited[2][SIZE]; //***********************算法实现***************************** void Init() { memset( map, 0, sizeof(map) ); memset( visited, 0, sizeof(visited) ); memset( dp, 0, sizeof(dp) ); } void Input() { cin >> m >> r; for( int i=0; i<r; i++ ) { int a, b; cin >> a >> b; map[a][b] = 1; } } //side=0 表示当前正在搜索A组 //side=1 表示当前正在搜索B组 //id 表示当前正在搜索的编号 void DFS( int side, int id ) { visited[side][id] = true; //如果当前搜索的是A组 if( side == 0 ) { //记录A组中的元素个数 aSize++; for( int i=1; i<=m; i++ ) { //搜索的是B组中对应的点 if( map[id][i] && !visited[1][i] ) { DFS( 1, i ); } } } else { bSize++; for( int j=1; j<=m; j++ ) { if( map[j][id] && !visited[0][j] ) { DFS( 0, j ); } } } } //利用二维背包计算 void Knapsack() { dp[0][0] = true; for( int x=m/2; x>=aSize-1; x-- ) { for( int y=m/2; y>=bSize-1; y-- ) { //if( dp[x - aSize][y - bSize] ) dp[x][y] = true; if( dp[x][y] || dp[x - aSize][y - bSize] ) dp[x][y] = true; } } } void Output() { for( int i=m/2; i>=0; i-- ) { if( dp[i][i] ) { cout << i << endl; break; } } } //************************main函数**************************** int main() { freopen( "in.txt", "r", stdin ); int caseNum; cin >> caseNum; while( caseNum-- ) { Init(); Input(); for( int i=1; i<=m; i++ ) { //跳过已经处理过的节点 if( visited[0][i] ) continue; //计算A、B中的人数 aSize = 0; bSize = 0; DFS( 0, i ); //利用二维背包计算 Knapsack(); } for( int i=1; i<=m; i++ ) { if( visited[1][i] ) continue; aSize = 0; bSize = 0; DFS( 1, i ); Knapsack(); } Output(); } return 0; }
代码(2),传递闭包 + DFS
#include <iostream> using namespace std; //***********************常量定义***************************** const int SIZE = 410; //*********************自定义数据结构************************* //********************题目描述中的变量************************ //监狱的人数 int m; //会发生冲突的对数 int r; //**********************算法中的变量************************** //map[i][j]表示i和j是否会冲突 int map[SIZE][SIZE]; int cnt; int visited[SIZE]; int groupA[SIZE/2]; int groupB[SIZE/2]; int dp[SIZE/2][SIZE/2]; //***********************算法实现***************************** void Input() { //输入数据 cin >> m >> r; //初始化map数组 memset( map, 0, sizeof(map) ); for( int j=1; j<=2*m; j++ ) { map[j][j] = 1; } for( int i=0; i<r; i++ ) { int a, b; cin >> a >> b; map[a][m+b] = 1; map[m+b][a] = 1; } } //求传递闭包 void Floyd() { for( int i=1; i<=2*m; i++) { for( int j=1; j<=2*m; j++ ) { if( map[i][j] == 1 ) { for( int k=1; k<=2*m; k++ ) { //if( map[j][k] == 1 ) map[i][k] = 1; map[i][k] = map[i][k] || ( map[i][j] && map[j][k] ); } } } } } //填充数组 groupA[], groupB[] void Build() { memset( visited, 0, sizeof(visited) ); memset( groupA, 0, sizeof(groupA) ); memset( groupB, 0, sizeof(groupB) ); cnt = 0; for( int i=1; i<=2*m; i++ ) { //跳过已访问的点 if( visited[i] == 0 ) { cnt++; for( int j=1; j<=2*m; j++ ) { if( map[i][j] == 1 ) { if( j<=m ) groupA[cnt]++; else groupB[cnt]++; visited[i] = 1; } } } } } void Knapsack() { memset( dp, 0, sizeof(dp) ); dp[0][0] = 1; //二维背包问题 for( int i=1; i<=cnt; i++ ) { for( int j=m/2; j>=groupA[i]; j-- ) { for( int k=m/2; k>=groupB[i]; k-- ) { //dp[j][k] = dp[j][k] || dp[j - groupA[i]][k - groupB[i]]; if( dp[j - groupA[i]][k - groupB[i]] ) { dp[j][k] = 1; } } } } for( int i=m/2; i>=0; i-- ) { if( dp[i][i] == 1 ) { cout << i << endl; break; } } } //************************main函数**************************** int main() { freopen( "in.txt", "r", stdin ); int caseNum; cin >> caseNum; while( caseNum-- ) { Input(); Floyd(); Build(); Knapsack(); } return 0; }
注:代码1已AC,代码2sample数据可过,提交WA,但思路应该没问题。哎,懒的调了。