二元关系可以用图来维护。
对罪犯i,记录如果i换监狱,那么多少个人会从A监狱到B监狱,多少人又会从B监狱到A监狱。(这是个连锁反应)
然后dp,f[i][j]表示i个人过去j个人过来是否可行。

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
struct edge
{
    int v,last;
}e[20010];
int in[10010],cnt=0;
void addedge(int x,int y)
{
    e[++cnt].v=y;
    e[cnt].last=in[x];
    in[x]=cnt;
}
int n;
int a,b;
bool vis[10010];
int f[1010][1010];
int p[10010],q[10010],top;
void dfs(int cur)
{
    int i,t;
    vis[cur]=1;
    if(cur<=n)a++;
    else b++;
    for(i=in[cur];i;i=e[i].last)
    {
        t=e[i].v;
        if(vis[t])continue;
        dfs(t);
    }
}
int main()
{
    int T,m,i,j,k,x,y,z;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        memset(vis,0,sizeof(vis));
        cnt=0;
        memset(f,0,sizeof(f));
        memset(in,0,sizeof(in));
        top=0;
        for(i=1;i<=m;i++)
        {
            scanf("%d%d",&x,&y);
            addedge(x,y+n);
            addedge(y+n,x);
        }
        for(i=1;i<=n*2;i++)
        {
            if(vis[i])continue;
            a=b=0;
            dfs(i);
            p[++top]=a;
            q[top]=b;
        }
        f[0][0]=1;
        for(i=1;i<=top;i++)
        {
            for(j=n/2;j>=p[i];j--)
            {
                for(k=n/2;k>=q[i];k--)
                {
                    f[j][k]|=f[j-p[i]][k-q[i]];
                }
            }
        }
        for(i=n/2;~i;i--)if(f[i][i])break;
        printf("%d\n",i);
    }
    return 0;
}
02-12 22:29