Vijos 3736 监狱重排

【问题描述】  

两所各有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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值