思路
原题链接(考试题不要求输出方案)
第一步:建反图
这道题如果想到了建反图思路就很明显了。
建反图:如果两个人不互相认识,就连一条边。对反图求出所有连通块。容易发现在反图里连通的点一定不是相互认识的且在反图中不同连通分量中的点一定是相互认识的,那么就是说如果在反图里有边那么在原图里一定不能在同一个集合中。对相邻的顶点进行二分图染色,如果相邻点可以被染成不同颜色,那么就可行,反之不行。那么这道题就是在反图上进行二分图染色判断。
第二步:
01
01
01背包
考虑这个染色问题的附加限制条件:要使两个集合的人数尽可能相等。
找出反图里所有的连通分支,每个连通分支分为两个集合,如果有k个连通分支,那么就有
2
∗
k
2*k
2∗k个集合,现在的问题就是把划分出来的
2
∗
k
2*k
2∗k个集合分成两组,使其数量尽可能相等。相当于有
2
∗
k
2*k
2∗k件物品,要装进一个背包,使背包中的人尽可能接近总人数的一半。
设
d
p
[
i
]
[
j
]
(
0
<
=
i
,
j
<
=
100
)
dp[i][j](0<=i,j<=100)
dp[i][j](0<=i,j<=100)表示第
1
1
1组为
i
i
i个人,第
2
2
2组为
j
j
j个人这个状态;
d
p
[
i
]
[
j
]
=
1
dp[i][j]=1
dp[i][j]=1表示存在。初始
d
p
[
0
]
[
0
]
=
1
dp[0][0] = 1
dp[0][0]=1;然后对每个连通分支
D
P
DP
DP,然后套
01
01
01背包判断当前决策是否可行,最后取个差值最小的。
考场
在考场上想到了二分图判定,但是没有想到建反图,于是考挂了
⋯
\cdots
⋯
正难则反
如果对于原来的图不可行的话考虑它的反图有什么特殊性质然后分析看看能不能找到解法。然后遇到选择方案数一般可以考虑
D
P
DP
DP,不要怕
D
P
DP
DP,一般好好想想,看看有没有类似模型或者看怎么转移,都可以设计出方程。
代码
随便写写竟然在团队里跑了个最优解系列 . j p g .jpg .jpg
#include <bits/stdc++.h>
using namespace std;
const int N=1005;
inline int read(){
int cnt=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
while(isdigit(c)){cnt=(cnt<<1)+(cnt<<3)+(c^48);c=getchar();}
return cnt*f;
}
int n,vis[N],c[N],sz[N][2],cnt;
bool a[N][N],f[N][N],mp[N][N];
bool dfs(int s,int nowc){
if(vis[s]) return c[s]==nowc;
vis[s]=true;c[s]=nowc;++sz[cnt][nowc];
for(int i=1;i<=n;++i)
if(a[s][i]&&!dfs(i,!nowc)) return false;
return true;
}
bool getpart(){
for(int i=1;i<=n;++i)
if(!vis[i]){
++cnt;if(!dfs(i,0)) return false;
}
return true;
}
inline void work(){
for(int i=0;i<=n;++i)
for(register int j=0;j<=n;++j)
f[i][j]=0;
f[0][0]=1;
for(int i=1;i<=cnt;++i)
for(register int j=n;j>=0;--j){
if(j>=sz[i][0]&&f[i-1][j-sz[i][0]]) f[i][j]=1;
if(j>=sz[i][1]&&f[i-1][j-sz[i][1]]) f[i][j]=1;
}
int ans=n;
for(int i=0;i<=n;++i)
if(f[cnt][i]) ans=min(ans,abs(n-i-i));
if(ans>=n) printf("No solution\n");
else printf("%d\n",ans);
}
signed main(){
int T=read();
while(T--){
n=read();cnt=0;
for(int i=1;i<=n;++i) vis[i]=c[i]=sz[i][0]=sz[i][1]=0;
for(int i=1;i<=n;++i)
for(register int j=1;j<=n;++j)
a[i][j]=read(),a[i][j]=!a[i][j];
for(int i=1;i<=n;++i)
for(register int j=1;j<i;++j)
if(a[i][j]||a[j][i])
a[i][j]=a[j][i]=true;
if(getpart()) work();
else printf("No solution\n");
}
return 0;
}