队员分组 (二分图+dp--差值最小问题)

复制代码
有 nn 个人从 11 至 nn 编号,相互之间有一些认识关系,你的任务是把一些人分成两组,使得:

每个人都被分到其中一组。
每个组都至少有一个人。
一组中的每个人都认识其他同组成员。
在满足上述条件的基础上,要求两组成员的人数之差(绝对值)尽可能小。请构造一种可行的方案。

请注意,xx 认识 yy 不一定说明 yy 认识 xx;xx 认识 yy 且 yy 认识 zz 不一定说明 xx 认识 zz。即认识关系是单向且不可传递的。

输入格式
输入的第一行是一个整数,代表总人数 nn。

第 22 到第 (n + 1)(n+1) 行,每行有若干个互不相同的整数,以 00 结尾,第 (i + 1)(i+1) 行的第 jj 个整数 a_{i, j}a 
i,j
​
 (00 除外)代表第 ii 个人认识 a_{i, j}a 
i,j
​
 。

输出格式
本题存在 Special Judge。

如果无解,请输出一行一个字符串 No solution。

如果有解,请输出两行整数,分别代表两组的成员。每行的第一个整数是该组的人数,后面以升序若干个整数代表该组的成员编号,数字间用空格隔开。

输入输出样例
输入 #1复制
5
2 3 5 0
1 4 5 3 0
1 2 5 0
1 2 3 0
4 3 2 1 0
输出 #1复制
3 1 3 5
2 2 4
说明/提示
数据规模与约定
对于全部的测试点,保证 2 \leq n \leq 1002≤n≤1001 \leq a_{i, j} \leq n1≤a 
i,j
​
 ≤n。
View Problem
复制代码

思路

  • 遇到分为2个组的时候,一般要考察二分图,特别是遇到2个点不能在一起的时候,就一定是二分图
  • 具体如何转化到2个点不能在一起,更具题目具体分析
  • 通过二分图,可以分为很多个连通板块,每一个连通板块可以分为 2种嘛(二分图,用2和3表示2种类型颜色(位运算^))
  • 关键是DP, 这里是让他们之间的差值最小,而且他们 2个数的和 是定值 n,
  • 因此只需要看看 连通块门,可以组合出多少类型数量(在一组内),从而另一组的数目也就知道,
  • 这相当于分组dp,dp【i】【j】=1表示这个数可以连上,
  • 想当重要: 如果 当前连通块只有1个数,那么他的类型3就是0,这个0也可以进行更新,特别重要!!!!(可以把特殊情况个解决!)
  • 应为这个背包,是用的二维数组,就可以回溯,
复制代码
#include <bits/stdc++.h>
using namespace std;
#define M 105
#define ri register int

template <class G> void read(G &x)
{
    x=0;int f=0;char ch=getchar();
    while(ch<'0'||ch>'9'){f|=ch=='-';ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x=f?-x:x;
    return ;
}

int n,m;
int arr[M][M];
vector <int> p[M];    
vector <int> q[M][M];
int tmp=0;
int flag[M];
int vis[M];
void dfs(int a)
{
    vis[a]=1;
    q[tmp][flag[a]].push_back(a);
    for(ri i=0;i<p[a].size();i++)
    {
        int b=p[a][i];
        if(flag[b]==flag[a])
        {
            printf("No solution");
            exit(0);
        }
        flag[b]=flag[a]^1;
        if(vis[b]) continue;
        dfs(b);
    }
}
int dp[M][M];
int pr[M][M];
int ANS1[M],ANS2[M];
int main(){
    
    read(n);
    for(ri i=1;i<=n;i++)
    {
       while(1)
       {
             int a;
             read(a);if(a==0) break;
             arr[i][a]=1;
       }
    }
    for(ri i=1;i<=n;i++)
    {
        for(ri j=i+1;j<=n;j++)
        {
            if(!arr[i][j]||!arr[j][i])
            {
                p[i].push_back(j);
                p[j].push_back(i);
            }
        }
    }

    for(ri i=1;i<=n;i++)
    {
        if(!flag[i])
        {
            ++tmp;
            flag[i]=2;
            dfs(i);
        }
    }
     dp[0][0]=1;

    for(ri i=1;i<=tmp;i++)
    {
        for(ri j=0;j<=n/2;j++)
        {
            int t=j-q[i][2].size();
            if(t>=0)  
            {   
              if(dp[i-1][t])dp[i][j]=1,pr[i][j]=2;
            }
            t=j-q[i][3].size();
            if(t>=0) 
            {
                if(dp[i-1][t]) 
                {
                    dp[i][j]=1;pr[i][j]=3;
                }
            }
        }
    }
    int ans=0;
    for(ri i=n/2;i>=1;i--)
    {
        if(dp[tmp][i]) 
        {
            ans=i;break;
        }
    }
    int ff=ans;
    int cur=n/2;
    for(ri i=tmp;i>=1;i--)
    {
        int a=pr[i][ans];
        for(ri j=0;j<q[i][a].size();j++)
        {
            int b=q[i][a][j];
            ANS1[b]=1;
        }
        ans-=q[i][a].size();
    }
    for(ri i=1;i<=n;i++)
    {
        if(ANS1[i]) continue;
        ANS2[i]=1;
    }
   printf("%d ",ff);
   for(ri i=1;i<=n;i++)
   {
       if(ANS1[i]) printf("%d ",i);
   }
   printf("\n");
   printf("%d ",n-ff);
   for(ri i=1;i<=n;i++)
   {
       if(ANS2[i]) printf("%d ",i);
   }
   return 0;    
    
}
View Code
复制代码

 

posted @   VxiaohuanV  阅读(97)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现

阅读目录(Content)

此页目录为空

点击右上角即可分享
微信分享提示