问题描述
In order to lower the risk of riots and escape attempts, the boards of two nearby prisons of equal prisoner capacity, have decided to rearrange their prisoners among themselves. They want to exchange half of the prisoners of one prison, for half of the prisoners of the other. However, from the archived information of the prisoners’ crime history, they know that some pairs of prisoners are dangerous to keep in the same prison, and that is why they are separated today, i.e. for every such pair of prisoners, one prisoners serves time in the first prison, and the other in the second one. The boards agree on the importance of keeping these pairs split between the prisons, which makes their rearrangement task a bit tricky. In fact, they soon find out that sometimes it is impossible to fulfil their wish of swapping half of the prisoners. Whenever this is the case, they have to settle for exchanging as close to one half of the prisoners as possible.
输入
On the first line of the input is a single positive integer n, telling the number of test scenarios to follow. Each scenario begins with a line containing two non-negative integers m and r, 1 < m < 200 being the number of prisoners in each of the two prisons, and r the number of dangerous pairs among the prisoners. Then follow r lines each containing a pair xi yi of integers in the range 1 to m,which means that prisoner xi of the first prison must not be placed in the same prison as prisoner yi of the second prison.
输出
For each test scenario, output one line containing the largest integer k <= m/2 , such that it is possible to exchange k prisoners of the first prison for k prisoners of the second prison without getting two prisoners of any dangerous pair in the same prison.
算法思想
根据题目的描述我们可以知道, 假如一号监狱中的i
号犯人与二号监狱中的j
号犯人之间存在危险, 那么在将i
号犯人从一号监狱移动到的二号监狱的同时, 必须也将j
号犯人移动到一号监狱. 这意味着, 我们可以将i
号犯人和j
号犯人的移动看作同时发生的.
更一般地, 一号监狱中的某个犯人, 可能与多个二号监狱中的犯人存在危险, 而这些二号监狱中的犯人中也可能不止与一号监狱中的那个犯人存在危险.
所以, 我们可以把犯人之间的关系抽象为一个无向图, 若两个犯人之间存在危险, 则有一条无向边将两个犯人相连. 我们借助深度优先搜索, 可以很方便地将图染色, 求出所有的连通分支.
我们将每个连通分支看作一个物品. 所有连通分支保存在数组w[][2]
中, w[i][0]
表示第i
个连通分支中来自一号监狱的人数, w[i][1]
表示第i
个连通分支中来自二号监狱的人数.
此时, 这个看似生疏的监狱分配问题, 已经可以转化为我们熟悉的01背包问题: 背包有两个口袋, 每个口袋的容量为m/2
, 我们要在保证两个口袋重量一致的前提下, 尽可能地选择总重量最大的物品.
代码实现
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
vector<int>graph[401];
bool vis[401];
int color[401]; //染色
int cnt_color; //颜色
int dp[101][101];
int w[401][2]; //物品
int n,m=0,r;
int a,b;
bool flag;
void init(){ //初始化
flag=0;
for(int i=1;i<=2*m;i++) graph[i].clear();
memset(vis,0,sizeof(vis));
memset(color,0,sizeof(color));
memset(dp,1<<7,sizeof(dp)); //条件限制
dp[0][0]=0;
memset(w,0,sizeof(w));
cnt_color=1;
}
void dfs(int s,int cnt){ //图染色
vis[s]=1;
color[s]=cnt;
for(int i=0;i<graph[s].size();i++){
int to=graph[s][i];
if(vis[to]) continue;
dfs(to,cnt);
}
}
void transform(){ //将连通分支转化为物品
for(int i=1;i<=m;i++){
w[color[i]][0]++;
}
for(int i=m+1;i<=2*m;i++){
w[color[i]][1]++;
}
}
void solve(){ //动态规划
for(int i=1;i<cnt_color;i++){
int w1=w[i][0],w2=w[i][1];
for(int j=m/2;j>=w1;j--){
for(int k=m/2;k>=w2;k--){
dp[j][k]=max(dp[j][k],dp[j-w1][k-w2]+w1+w2);
}
}
}
}
int main(){
cin>>n;
while(n--){
cin>>m>>r;
init();
for(int i=0;i<r;i++){
cin>>a>>b;
b+=m;
graph[a].push_back(b); //双向建图
graph[b].push_back(a);
}
for(int i=1;i<=2*m;i++){ //染色
if(vis[i]) continue;
dfs(i,cnt_color);
cnt_color++;
}
transform();
solve();
for(int i=m/2;i>0;i--){
if(dp[i][i]>=0){
cout<<dp[i][i]/2<<'\n';
flag=1;
break;
}
}
if(flag) continue;
cout<<0<<'\n';
}
return 0;
}