题目大意
有n个人,把他们分成非空的两组,使得每个人都被分到一组,并且同组的人要相互认识。要求两组的人数要尽量接近。多解的时候输出任意方案,无解时候输出No Solution。
思路
组的编号为0和1。因为同组的人必须相互认识,如果已知某个人在组0,那么不认识此人的就必须在组1。
让不认识关系组成一张图。
例如:
1 认识 2 3 5
2 认识 1 3 4 5
3 认识 1 2 5
4 认识 1 2 3
5 认识 1 2 3 4
那么不认识关系就是
2
4-1 4-3 4-5
代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
#define maxn 205
int G[maxn][maxn];//原关系图
vector<int> Ng[maxn];//不认识关系图
vector<int> C[4];//将每一组染色的数目
int color[maxn];//判断是否染色
int diff_C[maxn];
vector<int> list_color0[maxn];
vector<int> list_color2[maxn];
int CaseNum;
int N;
int dp[maxn][maxn*2];
int cnt = 0;
int cnt_color[3];
//dfs过程,给点染色,并且
bool dfs(int u,int father)
{
for(int i = 0 ; i<Ng[u].size(); i++)
{
int v = Ng[u][i];
if(v!=father)
{
if(color[v] == 0)//v没有染色
{
color[v] = -color[u];//把与u相连的点v染色成相反的颜色
cnt_color[color[v]+1]++;//计数color[u]的点
if(color[v] + 1 == 0 ) list_color0[C[0].size()].push_back(v);
if(color[v] + 1 == 2 ) list_color2[C[0].size()].push_back(v);
if(!dfs(v,u)) return false;
}
else if(color[v] == -color[u]){//已经染色 但是颜色正确
}
else//u已经染色 不存在这样的情况
return false;
}
}
return true;
}
//输出结果
void print_ans(int ans)
{
vector<int> team0,team2;
for(int i = C[0].size()-1; i >= 0; i--)
{
int t = 0;
if(dp[i][ans - diff_C[i] + N] == 1)
{
t = 0;
ans -=diff_C[i];
}
else
{
t = 1;
ans += diff_C[i];
}
if(t==1)
{
for(int j =0; j<list_color0[i].size(); j++)
team0.push_back(list_color0[i][j]);
for(int j =0; j<list_color2[i].size(); j++)
team2.push_back(list_color2[i][j]);
}
else
{
for(int j =0; j<list_color0[i].size(); j++)
team2.push_back(list_color0[i][j]);
for(int j =0; j<list_color2[i].size(); j++)
team0.push_back(list_color2[i][j]);
}
}
printf("%d",team0.size());
for(int j=0; j<team0.size(); j++)
printf(" %d",team0[j]);
printf("\n");
printf("%d",team2.size());
for(int j=0; j<team2.size(); j++)
printf(" %d",team2[j]);
printf("\n");
}
int main()
{
scanf("%d",&CaseNum);
while(CaseNum--)
{
memset(G,0,sizeof(G));
scanf("%d",&N);
for(int i =1; i<=N; i++)
{
int v;
while(scanf("%d",&v)&&v) G[i][v] = 1;
Ng[i].clear();
}
//构成不认识关系图
for(int i = 1; i<=N; i++)
{
for(int j = i+1; j<=N; j++)
{
if((!G[i][j])||(!G[j][i]))
{
Ng[i].push_back(j);
Ng[j].push_back(i);
}
}
}
/*
for(int i = 1;i<=N;i++){
for(int j = 0; j<Ng[i].size();j++){
printf("%d %d\n",i,Ng[i][j]);
}
printf("\n");
}
*/
//
cnt = 0;
cnt_color[0] = 0;
cnt_color[2] = 0;
C[0].clear();
C[1].clear();
for(int i = 0; i<N; i++)
{
list_color0[i].clear();
list_color2[i].clear();
}
bool flag = true;
for(int i =1 ; i<= N; i++) color[i] = 0; //置为没有染色
for(int i =1 ; i<= N; i++)
{
if(color[i] == 0) //若当前点没有染色
{
color[i] = -1;//将color[i]染色为-1
cnt_color[-1+1]++;//颜色-1的总数++
list_color0[C[0].size()].push_back(i);
flag = dfs(i,0);
if(flag == false) break;
C[0].push_back(cnt_color[0]);
C[1].push_back(cnt_color[2]);
//printf("%d %d %d\n",i,cnt_color[0],cnt_color[2]);
cnt_color[0] = 0;
cnt_color[2] = 0;
}
}
if(flag == false)
{
printf("No solution\n");
if(CaseNum) cout<<endl;
continue;
}
for(int i = 0; i< C[0].size(); i++)
{
//printf("%d %d\n",C[0][i],C[1][i]);
diff_C[i] = C[0][i]-C[1][i];
}
/*
for(int i=0; i<C[0].size(); i++)
{
for(int j = 0; j< list_color0[i].size(); j++)
{
printf("color0 %d \n",list_color0[i][j]);
}
for(int j = 0; j< list_color2[i].size(); j++)
{
printf("color2 %d \n",list_color2[i][j]);
}
}
*/
//使用dp算得每一组中的染色格子怎么放:
memset(dp,0,sizeof(dp));
dp[0][0+N] = 1;
for(int i = 0; i<C[0].size(); i++)
{
for(int j = -N; j<=N; j++)
{
if(dp[i][j+N])
{
dp[i+1][j+diff_C[i] + N] = 1;
dp[i+1][j-diff_C[i] + N] = 1;
}
}
}
for(int i = 0; i<=N; i++)
{
if(dp[C[0].size()][i+N])
{
print_ans(i);
break;
}
if(dp[C[0].size()][-i+N])
{
print_ans(i);
break;
}
}
if(CaseNum) cout<<endl;
}
return 0;
}
Hit
判断能否有解的时候,我出现了两个错误:
- 之前写的是如果下一个着色器已经有颜色,直接return false,这是个错误的写法,因为不认识关系是个四边形的环的时候,这种情况return false,但是其实是正确的。
- 还有一个dfs的地方写错,因为当下一个结点false的时候,其实整个dfs过程都是false。所以应该这样写:
if(!dfs(v,u)) return false;