一路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;
}