POJ 1112 WA到死

一路WA到死啊。这个题本身就比较有难度了,完全是给跪了,整体先是求补图,然后做二分染色,之后通过DP来求的最佳的分组状况。

看下POJ 上的分析,和一些网上的代码才能明白过来,特别是二分染色 是第一次遇到啊,这个dfs的写法还是比较恶心的。这里的DP也是非常的难想成做可能不可能的这种01的DP值。

首先,我们分析一下分组的要求:
1、把所有的人分成2组,每组至少有1人;
2、每组之间的人两两认识。
    非常明显,如果存在两个人A和B,A不认识B,或B不认识A,那么A和B一定不能分在同一组。因此,我们以人为结点重新构造一个图G。假如A和B不能分在同一组,那么就在G中增加一条无向边(A,B)。这样,我们就得到了一个较为“单纯”的模型。下面我们对这个模型进行简单分析。
    我们先研究G的一个连通分量K1。对于这个连通分量,可以先求出K1的生成树T1。对于K1中的任意结点a,假如a在T1中的深度为奇数,我们就把a加入点集S1;否则我们把a加入点集S2(S1,S2最初为空集)。显然最后S1,S2的交集为空。
    不难证明,如果存在不同结点p和q,p和q同属于S1或S2,而且G中存在边(p,q),那么要做到满足题目要求的分组是不可能的,应输出No solution。否则,我们就得到了连通分量K1的唯一分组方案:分为S1,S2两组。
    对于G中的每个连通分量Ki,我们可以求出相应的S1i,S2i。最后,我们的目的是把全部人分为2组。也就是说,对于i=1,2,3,...,m,我们必须决定把S1i中的人分到第1组,S2i中的人分到第2组,还是做刚好相反的处理。由于题目要求最后两组的总人数差最小,我们可以用动态规划的办法来确定究竟选取上面的哪种决策。
    不妨假设G中共有m个连通分量,记|S1i|=xi,|S2i|=yi(i=1,2,3,...,m)。我们用f[i,j]表示把前i个连同分量分为2组,且这两组总人数差的绝对值恰好为j是否可能。如果可能,f[i,j]=true;否则f[i,j]=false。初始条件是f[0,0]=true, f[0,x]=false(x=1,2,3,...)。然后我们可以按照如下方法确定f[i,j](0<i<=m, j>=0):
    f[i,j]= f[i-1, j-Abs(xi-yi)] or f[i-1, j+Abs(xi-yi)];
    当然,在求解的同时,我们可以记录路径。最后,res=min{i: f[m, i]=true}即为最佳分组的人数差,而它对应的路径就是我们要求的分组方案。

有写出算法的希望能给我发邮件,共同讨论进步嘛嘿嘿
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

#define MM 20010
#define NN 105
int n;
int a[110][110];
int edge_cnt;

struct node
{
    int v;
    struct node *nexts;
}edge[MM];
node *Link[NN];

int cnt[NN][2];
char col[NN];
int dfn[NN];
int time,sccc,ans[NN][2][NN];
char dp[NN][NN];
char pre[NN][NN];//记录dp路径
char flag[NN];

void add(int u,int v)
{
 edge[edge_cnt].v = v;
 edge[edge_cnt].nexts = Link[u];
 Link[u] = edge + edge_cnt++;
 edge[edge_cnt].v = u;
 edge[edge_cnt].nexts = Link[v];
  Link[v] = edge + edge_cnt++;
}

void DP()
{
  int i,j,t;
  dp[0][0]=1;
  for(i=1;i<=n;i++)
    dp[0][i]=0;
  for(i=1;i<=sccc;i++)
  {
      for(j=0;j<=n/2;j++)
      {
       dp[i][j]=0;
       t=j-cnt[i][0];
       if(t>=0&&dp[i-1][t])
       {
           dp[i][j]=1;
           pre[i][j]=0;
       }
        t=j-cnt[i][1];
         if(t>=0&&dp[i-1][t])
         {
             dp[i][j]=1;
             pre[i][j]=1;
         }
      }
  }
}

void print()
{
  int t,tmp,i,j,k;
  for(k=n/2;k>=1;k--)
  {
      if(dp[sccc][k]) break;
  }
  if(k==0) puts("No solution");
  else
  {
       tmp=k;
      memset(flag,0,sizeof(flag));
      for(i=sccc;i>=1;i--)
      {
          t=pre[i][tmp];
          tmp=tmp-cnt[i][t];
          for(j=1;j<=cnt[i][t];j++)
          {
              flag[ans[i][t][j]]=1;
          }
      }
      printf("%d",n-k);
      for(i=1;i<=n;i++)
      {
          if(flag[i]==0)
            printf(" %d",i);
      }
      puts("");
      printf("%d",k);
      for(int i=1;i<=n;i++)
        if(flag[i])
        printf(" %d",i);
      puts("");
  }
}

int dfs(int u)
{
  int v;
  dfn[u]=++time;
  ans[sccc][col[u]][++cnt[sccc][col[u]]]=u;//第一个连通分量属于col[u]着色的一个元素为u
  for(node *p=Link[u];p;p=p->nexts)
  {
    v=p->v;
    if(dfn[v]==0)
    {
        col[v]=!col[u];
        if(dfs(v)==0)
          return 0;
    }
    else
    {
        if(v!=u&&col[v]==col[u])
            return 0;
    }
  }
    return 1;
}

void solve()
{
    int i;
    memset(cnt,0,sizeof(cnt));//第i个连通分量里被染色成0或者1的颜色的点的个数
    memset(dfn,0, sizeof(dfn));//发现标号
    memset(col,0, sizeof(col));//color着色标记,分别为0和1两种值
    time=sccc=0;
    for(i=1;i<=n;i++)//不要用局部
    {
        if(dfn[i]==0)
        {
            ++sccc;//多了一个连通分量
            if(dfs(i)==0) break;
        }
    }
    if(i<=n) puts("No solution");//致死点
    else
    {
        DP();
        print();
    }
}


int main()
{
       int aaa;
       scanf("%d",&n);
       memset(a,0,sizeof(a));
       edge_cnt=0;
       memset(Link,0,sizeof(Link));
        for(int i=1;i<=n;i++)
       {
         while(scanf("%d",&aaa),aaa)
         {
             a[i][aaa]=1;
         }
       }
       for(int i=1;i<=n;i++)
       {
           for(int j=i+1;j<=n;j++)
           {
               if(a[i][j]==0||a[j][i]==0)
               {
                   add(i,j);
               }
           }
       }
      solve();
    return 0;
}


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值