1. 程式人生 > >圖論+dp poj 1112 Team Them Up!

圖論+dp poj 1112 Team Them Up!

題目連結:

題目大意:

有編號為1~n的n個人,給出每個人認識的人的編號,注意A認識B,B不一定認識A,讓你將所有的人分成兩組,要求每組的人相互認識,且兩組的人數要儘可能的接近。

求出每組的人的編號。

解題思路:

圖論+揹包(輸出物品)。

相互認識的關係不好確定分組,如果轉換思路,考慮相互不認識的情況就簡單好多,如果A不認識B,且B不認識C,那麼A和C必須分到同一組裡。所以就想到了,連通分量的染色,相鄰的兩個染不同的顏色(0或1),每一個連通分量分成兩組,並且相同顏色的人不能有邊(一定要相互認識),容易知道不同連通分量的人一定相互認識,否則是連通的。

然後問題就轉化為有多個連通分量,每個連通分量有兩組,每組必須屬於一個隊,求兩個隊的人數差最小,並分別輸出兩隊的人。

dp[i][j]表示到了第i個連通分量,且第一個隊的人數為j時是否能夠恰好湊齊。

ans[i][j]表示對應於狀態dp[i][j]時的選擇,0表示選擇0顏色的節點,1表示選擇1顏色的節點。

求出dp[num][]後,根據ans[num][]的值往前推,顏色選好後把所有的該顏色節點都加進去該隊裡去。

程式碼:

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<stack>
#include<list>
#include<queue>
#define eps 1e-6
#define INF 0x1f1f1f1f
#define PI acos(-1.0)
#define ll __int64
#define lson l,m,(rt<<1)
#define rson m+1,r,(rt<<1)|1
//#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;


//本題關鍵是建反圖,將相互認識的情況,轉化成相互不認識的情況
//思維逆向,這樣便於處理,因為不同的連通區一定相互認識,同一連通區內可以分層處理,相鄰的一定不相互認識
#define Maxn 110
struct Node
{
   int cnt[2];
   int sa[2][Maxn]; //存每個連通區間的兩類人數
}node[Maxn];
bool kn[Maxn][Maxn],nn[Maxn][Maxn];
bool vis[Maxn];
int n,num;
bool dp[Maxn][Maxn]; //dp[i]表示第一個team的人數
int ans[Maxn][Maxn]; //記錄到達第i個連通區且狀態為j時第一個隊的選擇
int aa[Maxn],bb[Maxn];//aa表示第一個隊的組成成員,


void dfs(int v,int p)
{
   node[num].cnt[p]++; //該連通區該顏色的人數
   node[num].sa[p][node[num].cnt[p]]=v;//標號
   for(int i=1;i<=n;i++)
   {
      if(!nn[v][i]||vis[i])
         continue;
      vis[i]=true;
      dfs(i,p^1);
   }
}

bool ok() //對每一個區間掃描是否有矛盾的
{
   for(int i=1;i<=num;i++)
   {
      for(int p=0;p<2;p++)
      {
         for(int k=1;k<=node[i].cnt[p];k++)
            for(int m=k+1;m<=node[i].cnt[p];m++)
            {
               int a=node[i].sa[p][k],b=node[i].sa[p][m];
               if(nn[a][b]) //同一聯通快內,同顏色不認識的話有矛盾
                  return false;
            }
      }
   }
   return true;
}

int main()
{
   int a;

   while(~scanf("%d",&n))
   {
      memset(kn,false,sizeof(kn));
      memset(nn,false,sizeof(nn));
      for(int i=1;i<=n;i++)
         while(scanf("%d",&a)&&a)
            kn[i][a]=true;
      for(int i=1;i<=n;i++)
         for(int j=1;j<=n;j++)
            if(!kn[i][j]||!kn[j][i])
               nn[i][j]=nn[j][i]=true; //建無向反圖
      memset(vis,false,sizeof(vis));
      num=0; //num表示連通區的個數
      for(int i=1;i<=n;i++) //對每一個連通區間 染色
         if(!vis[i])
         {
            num++;
            vis[i]=true;
            node[num].cnt[0]=node[num].cnt[1]=0;
            dfs(i,0);
         }
      if(!ok())
      {
         printf("No solution\n");
         continue;
      }
      memset(dp,false,sizeof(dp));
      memset(ans,0,sizeof(ans));
      dp[0][0]=true;
      for(int i=1;i<=num;i++) //簡單揹包,每個連通分量每種顏色必須進一個小隊
      {
         for(int j=n;j>=min(node[i].cnt[0],node[i].cnt[1]);j--) //第一個揹包
         {
            if(!dp[i][j]&&j>=node[i].cnt[0]&&dp[i-1][j-node[i].cnt[0]])
               dp[i][j]=true,ans[i][j]=0;
            if(!dp[i][j]&&j>=node[i].cnt[1]&&dp[i-1][j-node[i].cnt[1]])
               dp[i][j]=true,ans[i][j]=1;
         }
      }
      int gap=n,temp1=0,temp2=0;
      for(int i=1;i<=n;i++) //求出 差值最小的 兩支隊伍數
         if(dp[num][i]&&abs(i-(n-i))<gap)
            gap=abs(i-(n-i)),temp1=i;
      temp2=n-temp1;
      if(!temp1||!temp2)
         printf("No solution\n");
      else
      {
         //printf("%d %d\n",temp1,temp2);
         int p=0,q=0;
         for(int i=num;i>=1;i--)
         {
            //printf(":::%d\n",ans[i][temp1]);
            if(ans[i][temp1]) //逆向輸出,說明達到該狀態第一隊選擇了第1種
            {
               for(int j=1;j<=node[i].cnt[1];j++)
                  aa[++p]=node[i].sa[1][j];
               for(int j=1;j<=node[i].cnt[0];j++)
                  bb[++q]=node[i].sa[0][j];
               temp1-=node[i].cnt[1]; //注意第一隊要減去選擇了的人數 每個連通分量必須有人選,
            } //第一隊選擇了第0種
            else
            {
               for(int j=1;j<=node[i].cnt[0];j++)
                  aa[++p]=node[i].sa[0][j];
               for(int j=1;j<=node[i].cnt[1];j++)
                  bb[++q]=node[i].sa[1][j];
               temp1-=node[i].cnt[0];
            }
         }
         printf("%d",q);
         for(int i=1;i<=q;i++)
            printf(" %d",bb[i]);
         putchar('\n');
         printf("%d",p);
         for(int i=1;i<=p;i++)
            printf(" %d",aa[i]);
         putchar('\n');

      }

   }
   return 0;
}