时间限制:10000ms
单点时限:1000ms
内存限制:256MB
描述
小Ho最近遇到一个难题,他需要破解一个棋局。
棋局分成了n行,m列,每行有若干个棋子。小Ho需要从中选择若干行使得每一列有且恰好只有一个棋子。
比如下面这样局面:
其中1表示放置有棋子的格子,0表示没有放置棋子。
对于上面这个问题,小Ho经过多次尝试以后得到了解为选择2、3、4行就可以做到。
扫描二维码关注公众号,回复:
2531349 查看本文章
但是小Ho觉得自己的方法不是太好,于是他求助于小Hi。
小Hi:小Ho你是怎么做的呢?
小Ho:我想每一行都只有两种状态,选中和未被选中。那么我将选中视为1,未选中视为0。则每一种组合恰好对应了一个4位的01串,也就是一个4位的二进制数。
小Hi:恩,没错。
小Ho:然后我所做的就是去枚举每一个二进制数然后再来判定是否满足条件。
小Hi:小Ho你这个做法本身没什么问题,但是对于棋盘行数再多一点的情况就不行了。
小Ho:恩,我也这么觉得,那你有什么好方法么?
小Hi:我当然有了,你听我慢慢道来。
输入
第1行:1个正整数t,表示数据组数,1≤t≤10。
接下来t组数据,每组的格式为:
第1行:2个正整数n,m,表示输入数据的行数和列数。2≤n,m≤100。
第2..n+1行:每行m个数,只会出现0或1。
输出
第1..t行:第i行表示第i组数据是否存在解,若存在输出"Yes",否则输出"No"。
样例输入
2
4 4
1 1 0 1
0 1 1 0
1 0 0 0
0 1 0 1
4 4
1 0 1 0
0 1 0 0
1 0 0 0
0 0 1 1
样例输出
No
Yes
#include<iostream>
using namespace std;
const int maxN=110;
const int maxNode=10010;
int nodeNum;
int H[maxN],S[maxN];
//H[i]为第i行第一个为1的id,S[i]第i列1的个数
int Row[maxNode],Col[maxNode],U[maxNode],D[maxNode],L[maxNode],R[maxNode];
//行,列,上,下,左,右
//用来存上是哪个结点,下是哪个结点。如U[1]表示1这个结点的上。单独一个数就表示结点
//初始化
void init(int n,int m) //如果是邻接表的输入方式的话,构造出来的一定是个正方形。
{
for(int i=0;i<=m;i++)
{
S[i]=0;
L[i]=i-1;
R[i]=i+1;
U[i]=i;
D[i]=i; //底下一个col上下连接就就好理解了
}
L[0]=m; //双向循环链表
R[m]=0;
for(int i=0;i<=n;i++)
H[i]=-1;
}
//将第row行第col列第id个结点加入数据结构
void link(int row,int col,int id) //row,col的下标是从1开始的,
{
//存第i个结点的行列值,id表示是第几个结点,最开始的一排也是结点,称作列头结点,U,D,的值也是他们的id,有值的结点是从m+1开始的
Row[id]=row; //再次强调,最开始的一排也是结点,最开始的一排结点左右是0.
Col[id]=col; //存第i个结点的行列值
U[id]=U[col]; //用链表去理解
D[id]=col;
D[U[col]]=id;
U[col]=id;
if(H[row]==-1) //列设H数组是因为R,L存的不是对应的位置。而U,D,是对应ID的,所以不用设H数组。
H[row]=L[id]=R[id]=id;//每一行的开头都有个head的头结点,才发现H的头结点就是元素的ID,而且H是不画在图中的,H只是起一个临时作用。
else
{
L[id]=L[H[row]];
R[L[H[row]]]=id;
R[id]=H[row];
L[H[row]]=id;
}
S[col]++;
}
//删除第c列及所有在第c列上有1元素的行
void exact_remove(int c) //c是最开始的列结点
{
L[R[c]]=L[c];
R[L[c]]=R[c]; //这个只是最上面的一行,只是删了一个而已,但这样就已经实现功能了
//用行消的话能把列的头结点给消了。
for(int i=D[c];i!=c;i=D[i])
{
for(int j=R[i];j!=i;j=R[j])//才发现这里有个R[i]的细节。
//就是带行头结点链表的删除,本题行的头结点是不会被删除的
//最简状态是 0<--[0]-->0 或1<--[1]-->1这种。
{
U[D[j]]=U[j];
D[U[j]]=D[j];
--S[Col[j]];
}
}
}
//恢复第c列及所有在第c列上有元素的行
void exact_resume(int c)
{
for(int i=U[c];i!=c;i=U[i])
{
for(int j=L[i];j!=i;j=L[j])
{
D[U[j]]=j;
U[D[j]]=j;
++S[Col[j]];
}
}
L[R[c]]=R[L[c]]=c;
}
//d为递归深度
bool exact_dance(int d)
{
if(R[0]==0)
return true;
int c=R[0];
for(int i=R[0];i!=0;i=R[i])
{
if(S[i]<S[c])
c=i;
}
//选择结点数量最少的那一列 ,结点越少,正确的可能性就越大
exact_remove(c); //c是列号,又不是只去一次的,接下来是删除了的图的继续
for(int i=D[c];i!=c;i=D[i])
{
for(int j=R[i];j!=i;j=R[j])
{
exact_remove(Col[j]); //才意识到这是Col[j],还要移掉所有列。。。
//因为Col[i][j]左右没有变,所以即使有重复也没有关系
}
if(exact_dance(d+1))
return true;
for(int j=L[i];j!=i;j=L[j])
exact_resume(Col[j]);
}
exact_resume(c); //最后又有一步还原
return false;
}
int main()
{
int n,m,k,id;
cin>>k;
while(k--)
{
cin>>n>>m;
init(n,m);
id=m;
int value;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>value;
if(value==1)
{
id++;
link(i,j,id);
}
}
}
bool flag=exact_dance(0);
if(flag)
cout<<"Yes\n";
else
cout<<"No\n";
}
return 0;
}