【问题描述】
两所各有m名囚犯的监狱。为了为了降低骚乱和逃跑的风险,监狱的管理者们决定重新安排他们的囚犯:想在两个监狱之间至多交换m/2对囚犯。 然而,从囚犯的存档的犯罪历史分析,有若干对囚犯如果在同一个监狱将会更加危险。在交换之前,危险关系的囚犯对肯定是位于不同监狱,管理者希望在交换之后,他们也是分开的。
现在,希望你能就算最多能交换多少对囚犯,当然这个数量不能大于m/2。
【输入格式】
第一行一个整数T,表示数据组数。
每组数据的第一行为m,r,表示每所监狱中囚犯的数量,他们分别都从1..m编号,r表示有r对危险求反对: x,y,表示第一所监狱的x号囚犯与第二所监狱的y号囚犯的关系危险。
【输出格式】
每组数据输出一行一个整数,表示答案。
【输入样例】
3
101 0
3 3
1 2
1 3
1 1
8 12
1 1
1 2
1 3
1 4
2 5
3 5
4 5
5 5
6 6
7 6
8 7
8 8
【输出样例】
5003
【数据范围】
1 < m <= 200r<=m*m
首先,我们要处理一下题目中由于危险关系带来的限制,为了避免重复,我们将某一所监狱的犯人编号为1~m,同时将另一所监狱的犯人编号为m+1~2*m,当编号为x,y的犯人之间存在危险关系时,我们在x,y之间连出一条无向边。当所有的关系处理完毕后,我们可以发现2*m个点中形成了若干个连通分量。每个连通分量中的点按编号的大小归属于关押1~m号犯人的监狱或关押m+1~2*m号犯人的监狱,这样一来,每个连通分量便可以看作一块骨牌,上下两部分分别对应一个值(分量中属于某一监狱的人数),则题目变成了:有cc块骨牌(cc为连通分量数),选其中的任意多个,使选中骨牌的上下两部分的和均等于x,其中x要满足0<=x<=m/2,想到了用动态规划解决。
状态函数定义:dp(i,j,k)表示“前i块骨牌中选择了若干块骨牌,使得上面部分的和为j,下面部分的和为k”的可能性 如果状态可行则赋值为1,否则赋值为0。
对每一块骨牌i,我们要么选它,要么不选它,问题转化为典型的0/1背包问题。
状态转移方程:dp(i,j,k)=dp(i-1,j,k) || dp(i-1,j-sz[i][0],k-sz[i][1])
边界分析:dp(1,sz[1][0],sz[1][1])=dp(1,0,0)=1
答案分析:Ans=可行的dp(cc,x,x)(0<=x<=m/2)状态中最大的x。
具体实现过程用到了滚动数组。
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
const int maxm=200;
int T,m,r;
int sz[maxm*2+5][2],belong[maxm*2+5],vis[maxm*2+5],sum1[maxm+5],sum2[maxm+5],dp[2][maxm+5][maxm+5];
struct edge
{
int to;
int next;
}e[maxm*maxm+5];
int first[2*maxm+5],np;
void add(int u,int v)
{
e[++np]=(edge){v,first[u]};
first[u]=np;
return;
}
void init()
{
memset(e,0,sizeof(e));
memset(first,0,sizeof(first));
np=0;
scanf("%d%d",&m,&r);
int x,y;
for(int i=1;i<=r;i++)
{
scanf("%d%d",&x,&y);
add(x,y+m);
add(y+m,x);
}
return;
}
void DFS(int i,int cc)
{
vis[i]=1;
belong[i]=cc;
if(i<=m) sz[cc][0]++;
else sz[cc][1]++;
for(int p=first[i];p;p=e[p].next)
{
int j=e[p].to;
if(vis[j]) continue;
DFS(j,cc);
}
return;
}
void solve()
{
memset(belong,0,sizeof(belong));
memset(vis,0,sizeof(vis));
memset(sz,0,sizeof(sz));
int cc=0,a=0,b=0;
for(int i=1;i<=2*m;i++)
if(!vis[i])
DFS(i,++cc);
sum1[0]=sum2[0]=0;
for(int i=1;i<=cc;i++)
{
sum1[i]=sum1[i-1]+sz[i][0];
sum2[i]=sum2[i-1]+sz[i][1];
}
memset(dp,0,sizeof(dp));
dp[1][sz[1][0]][sz[1][1]]=1;
dp[1][0][0]=1;
for(int i=2;i<=cc;i++)
for(int x=0;x<=sum1[i];x++)
for(int y=0;y<=sum2[i];y++)
{
dp[i%2][x][y]=dp[i%2][x][y] || dp[(i-1)%2][x][y];
if(x>=sz[i][0] && y>=sz[i][1])
dp[i%2][x][y]=dp[i%2][x][y] || dp[(i-1)%2][x-sz[i][0]][y-sz[i][1]];
}
int ans=0;
for(int x=m/2;x>=0;x--)
if(dp[cc%2][x][x])
{
ans=x;
break;
}
cout<<ans<<"\n";
return;
}
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
scanf("%d",&T);
while(T--)
{
init();
solve();
}
return 0;
}